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
35 changes: 30 additions & 5 deletions library/src/calc_tables.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
*/

#include "calc_tables.hpp"
#include <algorithm>
#include <numeric>
#include <vector>

#include <pbn.hpp>
#include <solve_board.hpp>
#include <api/solve_board.hpp>
#include <solver_if.hpp>
#include <system/deal_fanout.hpp>
#include <system/memory.hpp>
#include <system/parallel_boards.hpp>
#include <system/scheduler.hpp>
Expand All @@ -23,11 +26,11 @@
extern Memory memory;
extern Scheduler scheduler;

Comment thread
tameware marked this conversation as resolved.
// Legacy overload (creates temporary context)
auto calc_all_boards_n(
Boards * bop,
SolvedBoards * solvedp,
int max_threads = 0) -> int;
int max_threads = 0,
bool difficulty_sort = true) -> int;


auto calc_single_common_internal(
Expand Down Expand Up @@ -110,7 +113,8 @@ auto calc_all_boards_n(
auto calc_all_boards_n(
Boards * bop,
SolvedBoards * solvedp,
int max_threads) -> int
int max_threads,
bool difficulty_sort) -> int
{
const int n = bop->no_of_boards;
if (n > MAXNOOFBOARDS)
Expand All @@ -137,11 +141,31 @@ auto calc_all_boards_n(
else
{
std::vector<SolverContext> contexts(static_cast<unsigned>(nthreads));

// Dispatch hardest boards first to shorten the parallel tail. This only
// helps across distinct deals (batch calc); for a single deal every board
// shares one fanout, so the sort is skipped (it would be a no-op anyway).
std::vector<int> order;
if (difficulty_sort)
{
std::vector<int> fanout(static_cast<unsigned>(n));
for (int i = 0; i < n; i++)
fanout[static_cast<unsigned>(i)] =
dds::internal::deal_fanout(bop->deals[i]);
order.resize(static_cast<unsigned>(n));
std::iota(order.begin(), order.end(), 0);
std::stable_sort(order.begin(), order.end(),
[&](const int a, const int b) {
return fanout[static_cast<unsigned>(a)] > fanout[static_cast<unsigned>(b)];
});
}

err = parallel_all_boards_n(n, nthreads,
[&](const int worker_id, const int bno) -> int {
return calc_single_common_internal(
contexts[static_cast<unsigned>(worker_id)], *bop, *solvedp, bno);
});
},
order.empty() ? nullptr : &order);
}

END_BLOCK_TIMER;
Expand Down Expand Up @@ -192,7 +216,8 @@ int STDCALL CalcDDtableN(
ind++;
}

int res = calc_all_boards_n(&bo, &solved, maxThreads);
// Single deal: all boards share one deal, so hardest-first sorting is a no-op.
int res = calc_all_boards_n(&bo, &solved, maxThreads, /*difficulty_sort=*/false);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trivial

Inline variable-name comment isn't necessary.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not? It seems useful to me. Were I writing the code I might have replaced it with a named constant, say DIFFICULTY_SORT_FALSE.

if (res != 1)
return res;

Expand Down
48 changes: 48 additions & 0 deletions library/src/system/deal_fanout.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
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 "deal_fanout.hpp"

#include <lookup_tables/lookup_tables.hpp>

namespace dds
{
namespace internal
{

auto deal_fanout(const Deal& dl) -> int
{
// The fanout for a given suit and a given player is the number
// of bit groups, so KT982 has 3 groups. In a given suit the
// maximum number over all four players is 13.
// A void counts as the sum of the other players' groups.

int fanout = 0;
int fanoutSuit, numVoids, c;

for (int h = 0; h < DDS_HANDS; h++)
{
fanoutSuit = 0;
numVoids = 0;
for (int s = 0; s < DDS_SUITS; s++)
{
c = static_cast<int>(dl.remainCards[h][s] >> 2);
fanoutSuit += group_data[c].last_group_ + 1;
if (c == 0)
numVoids++;
}
fanoutSuit += numVoids * fanoutSuit;
fanout += fanoutSuit;
}
Comment on lines +26 to +42

return fanout;
}

} // namespace internal
} // namespace dds
19 changes: 19 additions & 0 deletions library/src/system/deal_fanout.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include <api/dll.h>

namespace dds
{
namespace internal
{

/**
* @brief Cheap structural difficulty estimate (cards only, trump-independent).
*
* Per hand, sum the number of card groups per suit, with a bonus for voids.
* Used to dispatch the most difficult deals first in parallel batch calculations.
*/
auto deal_fanout(const Deal& dl) -> int;

} // namespace internal
} // namespace dds
39 changes: 34 additions & 5 deletions library/src/system/parallel_boards.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,51 @@ auto resolve_worker_count(
}


static auto is_permutation_of_range(
const std::vector<int>& order,
const int count) -> bool
{
Comment thread
zzcgumn marked this conversation as resolved.
std::vector<char> seen(static_cast<unsigned>(count), 0);
for (const int v : order)
{
if (v < 0 || v >= count || seen[static_cast<unsigned>(v)])
return false;
seen[static_cast<unsigned>(v)] = 1;
}
return true;
}


auto parallel_all_boards_n(
const int count,
const int worker_cap,
const std::function<int(int worker_id, int bno)>& process_board) -> int
const std::function<int(int worker_id, int bno)>& process_board,
const std::vector<int>* order) -> int
{
if (count <= 0)
{
return RETURN_NO_FAULT;
}

// Map a dispatch slot to the board number to process. With an order, hand out
// boards in that sequence (e.g. hardest first); otherwise in index order. The
// order is only honored when it is a valid permutation of [0, count); a
// malformed order falls back to index order to avoid invalid board indices.
const bool use_order =
(order != nullptr &&
static_cast<int>(order->size()) == count &&
is_permutation_of_range(*order, count));
Comment thread
tameware marked this conversation as resolved.
auto board_of = [&](const int slot) -> int {
return use_order ? (*order)[static_cast<unsigned>(slot)] : slot;
};

const int workers = resolve_worker_count(worker_cap, count);

if (workers == 1)
{
for (int bno = 0; bno < count; ++bno)
for (int slot = 0; slot < count; ++slot)
{
const int rc = process_board(0, bno);
const int rc = process_board(0, board_of(slot));
if (rc != RETURN_NO_FAULT)
{
return rc;
Expand All @@ -62,11 +90,12 @@ auto parallel_all_boards_n(
auto worker = [&](const int worker_id) {
for (;;)
{
const int bno = next.fetch_add(1, std::memory_order_relaxed);
if (bno >= count || first_error.load(std::memory_order_relaxed) != RETURN_NO_FAULT)
const int slot = next.fetch_add(1, std::memory_order_relaxed);
if (slot >= count || first_error.load(std::memory_order_relaxed) != RETURN_NO_FAULT)
{
break;
}
const int bno = board_of(slot);

const int rc = process_board(worker_id, bno);
if (rc != RETURN_NO_FAULT)
Expand Down
11 changes: 10 additions & 1 deletion library/src/system/parallel_boards.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#pragma once

#include <functional>
#include <vector>


/**
Expand All @@ -28,9 +29,17 @@ auto resolve_worker_count(int max_threads, int count) -> int;
* @param worker_cap Maximum worker threads; <= 0 uses hardware concurrency.
* @param process_board Called for each board; must return RETURN_NO_FAULT (1)
* on success. Receives the worker's thread index and board number.
* @param order Optional dispatch order: a permutation of [0, count) giving the
* sequence in which board numbers are handed out (e.g. hardest first to
* shorten the tail). When null/empty, boards are dispatched in index
* order. Only the dispatch order changes; @p process_board still receives
* the real board number, so result placement is unaffected. When
* non-null, the vector must remain valid and must not be mutated until
* this function returns because worker threads read it concurrently.
* @return First non-success code from @p process_board, or RETURN_NO_FAULT.
*/
auto parallel_all_boards_n(
int count,
int worker_cap,
const std::function<int(int worker_id, int bno)>& process_board) -> int;
const std::function<int(int worker_id, int bno)>& process_board,
const std::vector<int>* order = nullptr) -> int;
33 changes: 2 additions & 31 deletions library/src/system/scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
#include <cmath>
#include <iostream>

#include "deal_fanout.hpp"
#include "scheduler.hpp"
#include <fstream>
#include <iomanip>
#include <lookup_tables/lookup_tables.hpp>
#ifdef DDS_SCHEDULER
#include <system/time_stat_list.hpp>

Expand Down Expand Up @@ -270,7 +270,7 @@ void Scheduler::MakeGroups(const Boards& bds)
hands[b].NTflag = (strain == 4 ? 1 : 0);
hands[b].first = dl->first;
hands[b].strain = strain;
hands[b].fanout = Scheduler::Fanout(* dl);
hands[b].fanout = dds::internal::deal_fanout(*dl);
// hands[b].strength = Scheduler::Strength(* dl);

lp = &list[strain][key];
Expand Down Expand Up @@ -750,35 +750,6 @@ int Scheduler::Strength(const Deal& dl) const
}


int Scheduler::Fanout(const Deal& dl) const
{
// The fanout for a given suit and a given player is the number
// of bit groups, so KT982 has 3 groups. In a given suit the
// maximum number over all four players is 13.
// A void counts as the sum of the other players' groups.

int fanout = 0;
int fanoutSuit, numVoids, c;

for (int h = 0; h < DDS_HANDS; h++)
{
fanoutSuit = 0;
numVoids = 0;
for (int s = 0; s < DDS_SUITS; s++)
{
c = static_cast<int>(dl.remainCards[h][s] >> 2);
fanoutSuit += group_data[c].last_group_ + 1;
if (c == 0)
numVoids++;
}
fanoutSuit += numVoids * fanoutSuit;
fanout += fanoutSuit;
}

return fanout;
}


schedType Scheduler::GetNumber(const int thrId)
{
const unsigned tu = static_cast<unsigned>(thrId);
Expand Down
1 change: 0 additions & 1 deletion library/src/system/scheduler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ class Scheduler
void SortHands(const enum RunMode mode);

int Strength(const Deal& dl) const;
int Fanout(const Deal& dl) const;

void Reset();

Expand Down
25 changes: 25 additions & 0 deletions library/tests/system/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ cc_test(
],
)

# Internal deal fanout structural difficulty estimate
cc_test(
name = "deal_fanout_test",
size = "small",
srcs = ["deal_fanout_test.cpp"],
deps = [
"//library/src:testable_dds",
"//library/src/api:api_definitions",
"//library/src/lookup_tables:lookup_tables",
"//library/src/utility:constants",
"@googletest//:gtest_main",
],
)

# Worker-count helper unit test
cc_test(
name = "worker_count_test",
Expand All @@ -71,6 +85,17 @@ cc_test(
],
)

cc_test(
name = "parallel_boards_test",
size = "small",
srcs = ["parallel_boards_test.cpp"],
deps = [
"//library/src:testable_dds",
"//library/src/api:api_definitions",
"@googletest//:gtest_main",
],
)

# max_threads override equivalence + rename/alias initialisation test
cc_test(
name = "max_threads_equivalence_test",
Expand Down
Loading
Loading