Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 75 additions & 3 deletions library/src/calc_tables.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
*/

#include "calc_tables.hpp"
#include <atomic>
#include <thread>
#include <vector>

#include <pbn.hpp>
#include <solve_board.hpp>
#include <api/solve_board.hpp>
Expand Down Expand Up @@ -102,13 +106,81 @@ auto calc_all_boards_n(
return RETURN_NO_FAULT;
}

// Legacy overload: creates temporary context
// Legacy overload: parallel across boards, one SolverContext per worker.
auto calc_all_boards_n(
Boards * bop,
SolvedBoards * solvedp) -> int
{
SolverContext ctx;
return calc_all_boards_n(ctx, bop, solvedp);
const int n = bop->no_of_boards;
if (n > MAXNOOFBOARDS)
return RETURN_TOO_MANY_BOARDS;

for (int k = 0; k < MAXNOOFBOARDS; k++)
solvedp->solved_board[k].cards = 0;

const int nthreads = std::max(1,
std::min(static_cast<int>(std::thread::hardware_concurrency()), n));

if (nthreads <= 1)
{
SolverContext ctx;
return calc_all_boards_n(ctx, bop, solvedp);
}

std::vector<SolverContext> contexts(static_cast<unsigned>(nthreads));
std::atomic<int> next_board{0};
std::atomic<int> first_error{0};

auto worker = [&](const int worker_id) {
for (;;)
{
const int bno = next_board.fetch_add(1, std::memory_order_relaxed);
if (bno >= n || first_error.load(std::memory_order_relaxed) != 0)
break;

const int err = calc_single_common_internal(
contexts[static_cast<unsigned>(worker_id)], *bop, *solvedp, bno);
if (err != 1)
{
int expected = 0;
first_error.compare_exchange_strong(
expected, err, std::memory_order_relaxed);
break;
}
}
};

START_BLOCK_TIMER;
{
std::vector<std::thread> threads;
threads.reserve(static_cast<unsigned>(nthreads));
try
{
for (int i = 0; i < nthreads; ++i)
threads.emplace_back(worker, i);
}
catch (...)
{
for (auto & t : threads)
if (t.joinable())
t.join();
throw;
}
for (auto & t : threads)
t.join();
}
END_BLOCK_TIMER;

if (const int err = first_error.load(); err != 0)
return err;

solvedp->no_of_boards = n;

#ifdef DDS_SCHEDULER
scheduler.PrintTiming();
#endif

return RETURN_NO_FAULT;
}


Expand Down
7 changes: 3 additions & 4 deletions library/tests/args.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,9 @@ void usage(
"-s, --solver One of: solve, calc, play, par, dealerpar.\n" <<
" (Default: solve)\n" <<
"\n" <<
"-n, --numthr n Maximum number of threads (legacy option).\n" <<
" (Default: 0 uses DDS/library defaults; when using\n" <<
" the modern SolverContext API, prefer configuring\n" <<
" threads via SolverConfig instead of this option.)\n" <<
"-n, --numthr n Worker threads for solve/calc/play batches.\n" <<
" 0 = auto (hardware concurrency), 1 = sequential.\n" <<
" (Default: 0)\n" <<
"\n" <<
"-m, --memory n Total DDS memory size in MB (legacy option).\n" <<
" (Default: 0 uses DDS/library defaults; when using\n" <<
Expand Down
4 changes: 4 additions & 0 deletions library/tests/dtest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ int main(int argc, char * argv[])
DDSInfo info;
GetDDSInfo(&info);
cout << info.systemString << endl;
if (options.num_threads_ == 0)
cout << "dtest worker threads: auto\n";
else
cout << "dtest worker threads: " << options.num_threads_ << "\n";

real_main(argc, argv);

Expand Down
95 changes: 95 additions & 0 deletions library/tests/dtest_parallel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
DDS, a bridge double dummy solver.

Copyright (C) 2006-2014 by Bo Haglund /
2014-2018 by Bo Haglund & Soren Hein.

See LICENSE and README.
*/

#include "dtest_parallel.hpp"

#include <algorithm>
#include <atomic>
#include <thread>
#include <vector>

#include <api/dll.h>


int dtest_effective_threads(const int requested, const int workload)
{
if (workload <= 1)
return 1;

const unsigned hw = std::thread::hardware_concurrency();
const int auto_count = hw > 0 ? static_cast<int>(hw) : 1;

int n = requested > 0 ? requested : auto_count;
n = std::max(1, std::min(n, workload));
return n;
}


int dtest_run_parallel(
const int count,
const int requested_threads,
const std::function<int(int)> & body)
{
if (count <= 0)
return RETURN_NO_FAULT;

const int nthreads = dtest_effective_threads(requested_threads, count);
if (nthreads <= 1)
{
for (int i = 0; i < count; ++i)
{
const int rc = body(i);
if (rc != RETURN_NO_FAULT)
return rc;
}
return RETURN_NO_FAULT;
}

std::atomic<int> next{0};
std::atomic<int> first_error{0};

auto worker = [&] {
for (;;)
{
const int i = next.fetch_add(1, std::memory_order_relaxed);
if (i >= count || first_error.load(std::memory_order_relaxed) != 0)
break;

const int rc = body(i);
if (rc != RETURN_NO_FAULT)
{
int expected = 0;
first_error.compare_exchange_strong(
expected, rc, std::memory_order_relaxed);
break;
}
}
};

std::vector<std::thread> threads;
threads.reserve(static_cast<unsigned>(nthreads));
try
{
for (int t = 0; t < nthreads; ++t)
threads.emplace_back(worker);
}
catch (...)
{
for (auto & th : threads)
if (th.joinable())
th.join();
throw;
}

for (auto & th : threads)
th.join();

const int err = first_error.load(std::memory_order_relaxed);
return err != 0 ? err : RETURN_NO_FAULT;
}
28 changes: 28 additions & 0 deletions library/tests/dtest_parallel.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
DDS, a bridge double dummy solver.

Copyright (C) 2006-2014 by Bo Haglund /
2014-2018 by Bo Haglund & Soren Hein.

See LICENSE and README.
*/

#pragma once

#include <functional>

/// Resolve the worker thread count for a dtest batch.
///
/// @param requested Thread count from -n (0 = auto from hardware).
/// @param workload Number of independent items in the batch.
/// @return Thread count in [1, workload].
int dtest_effective_threads(int requested, int workload);

/// Run @p body for each index in [0, count) using up to @p requested_threads workers.
///
/// @p body must return RETURN_NO_FAULT (1) on success.
/// @return First non-success code from @p body, or RETURN_NO_FAULT.
int dtest_run_parallel(
int count,
int requested_threads,
const std::function<int(int)> & body);
49 changes: 43 additions & 6 deletions library/tests/loop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
#include "TestTimer.hpp"
#include "compare.hpp"
#include "print.hpp"
#include <vector>

#include "cst.hpp"
#include "dtest_parallel.hpp"

using std::cout;
using std::endl;
Expand All @@ -26,6 +30,7 @@ using std::right;
#define BATCHTIMES

extern TestTimer timer;
extern OptionsType options;


void loop_solve(
Expand Down Expand Up @@ -57,7 +62,28 @@ void loop_solve(

timer.start(count);
int ret;
if ((ret = SolveAllChunks(bop, solvedbdp, 1)) != RETURN_NO_FAULT)
if (dtest_effective_threads(options.num_threads_, count) <= 1)
{
ret = SolveAllBoardsSeq(bop, solvedbdp);
}
else
{
solvedbdp->no_of_boards = count;
ret = dtest_run_parallel(count, options.num_threads_,
[&](const int j) -> int {
FutureTricks fut;
const int res = SolveBoardPBN(
bop->deals[j], bop->target[j], bop->solutions[j], bop->mode[j],
&fut, 0);
if (res == RETURN_NO_FAULT)
{
solvedbdp->solved_board[j] = fut;
return RETURN_NO_FAULT;
}
return res;
});
}
if (ret != RETURN_NO_FAULT)
{
cout << "loop_solve: i " << i << ", return " << ret << "\n";
exit(0);
Expand Down Expand Up @@ -113,9 +139,8 @@ bool loop_calc(
strcpy(dealsp->deals[j].cards, deal_list[i+j].remainCards);

timer.start(count);
int ret;
if ((ret = CalcAllTablesPBN(dealsp, -1, filter, resp, parp))
!= RETURN_NO_FAULT)
const int ret = CalcAllTablesPBN(dealsp, -1, filter, resp, parp);
if (ret != RETURN_NO_FAULT)
{
cout << "loop_calc: i " << i << ", return " << ret << "\n";
exit(0);
Expand Down Expand Up @@ -270,8 +295,20 @@ bool loop_play(

timer.start(count);
int ret;
if ((ret = AnalyseAllPlaysPBN(bop, playsp, solvedplp, 1))
!= RETURN_NO_FAULT)
if (dtest_effective_threads(options.num_threads_, count) <= 1)
{
ret = AnalyseAllPlaysPBN(bop, playsp, solvedplp, 1);
}
else
{
solvedplp->no_of_boards = count;
ret = dtest_run_parallel(count, options.num_threads_,
[&](const int j) -> int {
return AnalysePlayPBN(
bop->deals[j], playsp->plays[j], &solvedplp->solved[j], 0);
});
}
if (ret != RETURN_NO_FAULT)
{
printf("loop_play i %i: Return %d\n", i, ret);
cout << "loop_play: i " << i << ": " << "return " << ret << "\n";
Expand Down
Loading