Skip to content

Use coot::get_max_number_of_threads() in calc_atom_map_edcalc - #384

Open
martinemnoble1 wants to merge 1 commit into
pemsley:mainfrom
martinemnoble1:fix/edcalc-honour-thread-limit
Open

Use coot::get_max_number_of_threads() in calc_atom_map_edcalc#384
martinemnoble1 wants to merge 1 commit into
pemsley:mainfrom
martinemnoble1:fix/edcalc-honour-thread-limit

Conversation

@martinemnoble1

Copy link
Copy Markdown
Contributor

Summary

calc_atom_map_edcalc() sizes its thread pool from std::thread::hardware_concurrency()
directly:

// coot-utils/edcalc.cc:183
unsigned int n_threads = std::thread::hardware_concurrency();
if (n_threads == 0) n_threads = 4;

so set_max_number_of_threads() (and COOT_N_THREADS) have no effect on it. This
patch changes it to coot::get_max_number_of_threads(), which still falls back to
the system CPU count when nothing has been set — so callers that never set a limit
behave exactly as before, and callers that do set one are now honoured.

Why this matters

On a workstation an over-large thread count is a scheduling inefficiency. Under
WebAssembly it is a correctness problem: pthreads come from a fixed-size pool
established at link time (-sPTHREAD_POOL_SIZE). When that pool is exhausted,
emscripten's fallback requires returning to the JS event loop before the thread can
start — and calc_atom_map_edcalc's spawn-then-join() loop never does. The result
is a deadlock rather than a slowdown.

Because the count tracked core count, the failure was hardware-dependent: it
reproduced on a 10-core machine and would have appeared on any machine with more
cores regardless of how the pool was sized.

Evidence

Reached via density_correlation_analysismap_to_model_correlation_per_residue
calc_atom_map, in an embedded Moorhen session.

The thread-count setting itself was working correctly. Instrumenting both accessors
to print the value and the address of coot_n_threads:

set_max_number_of_threads(3)   &coot_n_threads=0x265cac
get_max_number_of_threads -> 3 &coot_n_threads=0x265cac

Same object, correct value. But coot's own timing line in the same run reported:

TIMINGS:: calc_atom_map_edcalc: ... n_threads 8      <-- ignores the setting

After the change, on the same structure:

TIMINGS:: calc_atom_map_edcalc: ... n_threads 3

Tracing emscripten's worker pool over a full session (model + map + validation):

before after
workers per burst 8–10 3
peak concurrent pthreads 13 8 (all of it the constructor's thread_pool(8))
PTHREAD_POOL_SIZE=8 deadlocks on map load loads cleanly, 2 slots always spare

Related sites — not changed here

std::thread::hardware_concurrency() is used the same way in seven other places.
None are compiled into the WebAssembly build, so I have no evidence from them and
have deliberately left them alone — but they will equally ignore
set_max_number_of_threads() for desktop and Python callers, so you may want them
swept in the same change:

  • coot-utils/crowther.cc:473, 587, 681
  • ligand/molecular-replacement.cc:380, 543
  • docking/semiflex-refine.cc:987
  • docking/rigid-body-dock.cc:615

Two nearby thread counts are hardcoded rather than hardware-derived
(coot-utils/coot-map-utils.cc:4184 = 4, :4782 = 8). They also bypass the
setting, but being fixed they cannot scale with core count, so they are a milder
case.

Also noticed

map_to_model_correlation_stats_per_residue_run logs an error when the thread count
exceeds the number of work ranges — with 3 threads and 2 ranges it emits:

ERROR::   bad thread index 2.000000 vs 2.000000

The loop runs to n_threads but only pushes work while i_thread < ranges.size(),
so the final iteration logs an error on a case that is expected. Harmless, but noisy.
Happy to fix separately if useful.

Testing

Built for WebAssembly (emscripten 6.0.0, 32- and 64-bit) and exercised through
Moorhen: model load, map load, contouring, and the density-correlation validation
path. No behaviour change observed other than the thread count; validation output
is unchanged.

I have not been able to test the desktop or Python builds — worth a second pair of
eyes on whether any caller depends on this function saturating the machine by
default. Note the fallback preserves that behaviour unless a limit is explicitly set.

calc_atom_map_edcalc() sized its thread count from
std::thread::hardware_concurrency() directly, so set_max_number_of_threads()
and COOT_N_THREADS had no effect on it.

get_max_number_of_threads() still falls back to the system CPU count when
nothing has been set, so callers that never set a limit are unaffected; callers
that do set one are now honoured.

On a workstation an over-large thread count is only a scheduling inefficiency.
Under WebAssembly it is a correctness problem: pthreads are drawn from a
fixed-size pool established at link time (-sPTHREAD_POOL_SIZE), and when it is
exhausted emscripten's fallback needs the JS event loop to run before the thread
can start -- which this function's spawn-then-join loop never allows. The result
is a deadlock rather than a slowdown, and because the count tracked core count
it was hardware-dependent.

Measured in an embedded Moorhen session, reached via
density_correlation_analysis -> map_to_model_correlation_per_residue ->
calc_atom_map. The thread-count setting itself was working: instrumenting both
accessors to print the value and the address of coot_n_threads showed
set(3) and get -> 3 on the same object, while coot's own timing line in the same
run reported "n_threads 8". After this change it reports "n_threads 3", peak
concurrent pthreads over a full session drops from 13 to 8, and a build with
PTHREAD_POOL_SIZE=8 goes from deadlocking on map load to loading cleanly.

The same pattern appears in seven other places that are not part of the
WebAssembly build and are left unchanged here: coot-utils/crowther.cc (three),
ligand/molecular-replacement.cc (two), docking/semiflex-refine.cc and
docking/rigid-body-dock.cc.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant