Bigger batches - #273
Conversation
Add unbounded CalcAllTablesx/PBNx that expand all dealxstrain boards into a single parallel_all_boards_n dispatch (heap-backed), and point loop_calc at CalcAllTablesPBNx so 1000-deal batches are no longer 25x40 chunks. Legacy CalcAllTablesN stays capped at MAXNOOFTABLES. Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the unbounded calc entry points consistent with local API casing and regenerate the JNI export lists so the shared library still exports them. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces new “unbounded” CalcAllTables APIs that accept more than MAXNOOFTABLES deals by expanding deal×strain work onto heap-backed board lists and dispatching the entire batch as a single parallel_all_boards_n job, improving throughput for large batches.
Changes:
- Add new exported C APIs
CalcAllTablesXandCalcAllTablesPBNXand wire them into JNI symbol export lists. - Implement large-batch “single parallel job” execution in
CalcAllTablesX, and add a test seam to observe the last dispatched board-count. - Add new system tests for the unbounded APIs and update the test harness calc loop to use
CalcAllTablesPBNXfor full-file batches.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| library/tests/system/calc_all_tables_x_test.cpp | New system tests covering unbounded CalcAllTables* behavior and single-job dispatch shape. |
| library/tests/system/BUILD.bazel | Registers the new system test target. |
| library/tests/loop.hpp | Updates loop calc comment to match the new unbounded calc path. |
| library/tests/loop.cpp | Switches calc loop to a single CalcAllTablesPBNX call over the whole deal list. |
| library/src/system/parallel_boards.hpp | Adds test seam accessor for last job’s board count. |
| library/src/system/parallel_boards.cpp | Stores last job’s board count for tests to assert single-job dispatch. |
| library/src/calc_tables.hpp | Declares a helper exposing the legacy chunk-deal sizing. |
| library/src/calc_tables.cpp | Implements CalcAllTablesX/CalcAllTablesPBNX and the chunk-deals helper. |
| library/src/api/dll.h | Exposes the new unbounded APIs in the public C header with documentation. |
| jni/version_script.lds | Exports new symbols for the JNI shared library. |
| jni/exported_symbols.lds | Exports new symbols for the JNI shared library (underscore-prefixed list). |
Comments suppressed due to low confidence (1)
library/src/calc_tables.cpp:567
CalcAllTablesPBNXshould also validatetrumpFilterfor null and use a non-size error code for null pointer arguments. As written,trumpFilter == nullptrwill be dereferenced later inCalcAllTablesX.
if (numDeals < 0)
return RETURN_TOO_MANY_TABLES;
if (numDeals == 0)
return RETURN_NO_FAULT;
if (deals == nullptr || results == nullptr)
return RETURN_TOO_MANY_TABLES;
Null deals, results, or trumpFilter are not size errors; match other C ABI pointer checks and cover both binary and PBN entry points in tests. Co-authored-by: Cursor <cursoragent@cursor.com>
Missing par is an invalid argument, not a table-count error; cover it in the null-pointer tests. Co-authored-by: Cursor <cursoragent@cursor.com>
Fill only the in-bounds deals slots while setting no_of_tables past MAXNOOFTABLES so the rejection path does not rely on undefined behavior. Co-authored-by: Cursor <cursoragent@cursor.com>
Still exercise >MAXNOOFTABLES deals and single-job dispatch, but avoid the 5× board count that timed out macOS ThreadSanitizer CI at 300s. Co-authored-by: Cursor <cursoragent@cursor.com>
Bail out when number is non-positive so the unsigned size cast cannot underflow, and copy remainCards with bounded strncpy. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
library/tests/loop.hpp:43
- The comment says this calculate loop is implemented as a single
CalcAllTablesPBNXcall, but the function signature still exposes legacy batch structs (DdTableDealsPBN*,DdTablesRes*,AllParResults*) that are no longer populated. This mismatch makes it hard to tell what callers can rely on.
/// Calculate loop: CalcAllTablesPBNX for the full deal list in one parallel job.
bool loop_calc(
DdTableDealsPBN * dealsp,
DdTablesRes * resp,
AllParResults * parp,
DealPBN * deal_list,
DdTableResults * table_list,
const int number,
const int stepsize);
It now owns flat X-API vectors, so dealsp/resp/parp/stepsize only obscured the real CalcAllTablesPBNX path. Co-authored-by: Cursor <cursoragent@cursor.com>
It was only referenced by a vestigial test; CalcAllTablesX does not chunk by MAXNOOFBOARDS, so keep that helper out of the production header. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
library/src/calc_tables.cpp:466
CalcAllTablesXis a C ABI entry point but it performs unbounded heap allocations (std::vector/std::stable_sort) that can throw (e.g.std::bad_alloc). Letting exceptions escape anextern "C"frame is undefined behavior; the pure-C shim inlibrary/src/api/dds_c_api.cppexplicitly wraps entry points in catch-all handlers for this reason. Consider adding a catch-all around the body and returningRETURN_UNKNOWN_FAULTon exception.
// Expand every deal×included-strain into one board list and solve in a
// single parallel_all_boards_n job (heap-backed). This is the ddss-style
// large-batch shape; legacy CalcAllTablesN remains capped at MAXNOOFTABLES.
const int nboards = numDeals * included;
std::vector<Deal> boards(static_cast<unsigned>(nboards));
std::vector<std::array<int, DDS_HANDS>> scores(static_cast<unsigned>(nboards));
Heap allocations can throw; map any throw to RETURN_UNKNOWN_FAULT so exceptions never unwind into foreign C callers. Co-authored-by: Cursor <cursoragent@cursor.com>
Update the test seam before the count <= 0 early return so a prior job cannot leave a stale value. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (2)
library/tests/system/calc_all_tables_x_test.cpp:107
std::strncpy()may leavedeal.cardsunterminated. Add an explicit terminator so the invalid-PBN test can’t accidentally depend on buffer contents past the copied bytes.
// Must not start with N/E/S/W (case-insensitive) or convert_from_pbn may
// attempt a partial parse and fail later with a different code.
std::strncpy(deal.cards, "ZZZ:not-a-deal", sizeof(deal.cards));
DdTableResults result{};
int filter[DDS_STRAINS] = {0, 0, 0, 0, 0};
library/tests/system/calc_all_tables_x_test.cpp:208
- This
forloop usesstd::strncpy()into a fixed-size C buffer without guaranteeing NUL-termination. If the PBN string ever grows to 80 bytes,convert_from_pbn()could read past the buffer. Add explicit termination (and braces since there will be multiple statements).
for (int i = 0; i < kNum; i++)
std::strncpy(pbn_deals[static_cast<unsigned>(i)].cards, pbn, sizeof(pbn_deals[0].cards));
strncpy can leave fixed-size DdTableDealPBN::cards without a terminator; copy_pbn_cards always writes one. Co-authored-by: Cursor <cursoragent@cursor.com>
Hardest-first dispatch only helps with multiple workers; avoid O(n log n) overhead on large single-thread batches, matching calc_all_boards_n. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
library/src/calc_tables.cpp:567
CalcAllTablesPBNXdefinition also hard-codestrumpFilter[5]even though the public header usesDDS_STRAINS. Keeping the definition consistent avoids a stale dimension ifDDS_STRAINSever changes.
int STDCALL CalcAllTablesPBNX(
int numDeals,
DdTableDealPBN const * deals,
int mode,
int const trumpFilter[5],
DdTableResults * results,
Match dll.h declarations and avoid a magic 5 that can drift from the strain count constant. Co-authored-by: Cursor <cursoragent@cursor.com>
Export new CalcAllTablesX / CalcAllTablesPBNX APIs that remove the batch size limitation, similar to @BSalita's ddss. Speeds up large Calc batches substantially. Extends the ABI but does not mutate it, so should (still) be a drop-in replacement for 2.9. I've not tested that.
We ought to be able improve performance for Solve batches - that can wait for a future PR.
See these Issues:
#139
#221
#260
#269
Benchmarks versus v2.9, ddss, and the 3.0 develop head, averaged over 10 runs: