Build Packages / Create release (push) Successful in 26s
Build Packages / Build documentation (push) Successful in 1m43s
Build Packages / build:portable:macos-arm64:nocuda (push) Successful in 5m7s
Build Packages / build:portable:linux-x86_64:nocuda (push) Successful in 11m48s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 7m22s
Build Packages / build:portable:linux-x86_64:cuda (push) Successful in 12m24s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m30s
Build Packages / build:portable:windows-x86_64:nocuda (push) Successful in 25m29s
Build Packages / build:portable:windows-x86_64:cuda (push) Successful in 29m41s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 21m44s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 24m41s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 26m9s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 26m3s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 28m28s
Build Packages / Generate python client (push) Successful in 27s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 31m16s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 20m44s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 26m27s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 22m52s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 27m44s
Build Packages / Unit tests (push) Successful in 1h46m55s
Build Packages / Validation Battery (push) Successful in 52m50s
* Rugnux: Several rotation sweeps of one crystal are merged as one dataset (`rugnux a_master.h5 b_master.h5`), on a common indexing and cell, with a non-isomorphous sweep left out and reported; `--sweeps separate` processes each sweep on its own. Bruker `.sfrm` frames and imgCIF files with a full goniometer axis chain are read. * Rugnux: More robust rotation processing - a retry with a more sensitive spot finder when no lattice is found, a self-consistent detector distance, long axes and doubled cells tested as hypotheses, a `-C` supercell multiple stepped down, and each ice ring merged, down-weighted or rejected on its own. * Rugnux: Serial stills are merged with a partiality-aware error model, reported as `ERROR_MODEL_A/B/C` with no ISa or R_merge/R_meas/R_pim; runs without `-S` have an overall intensity scale about 0.86-0.88 times that of earlier versions, with unchanged statistics. A second crystal form is left out of the merge and reported, the indexing ambiguity is resolved more reliably, and `--model` no longer influences processing. * Rugnux: The reported CC1/2 is the standard unweighted one, which can be lower than before on sweeps with weak frames; ISa comes with a 95% interval (`ISA_LOW`/`ISA_HIGH`). * Rugnux: Model refinement adds anisotropic/TLS, local NCS and twin refinement where R-free supports them; building waters and relabelling ions is now off by default (`--refine-solvent` turns it on). * NXmx: A detector of several modules is written as `module_0` ... `module_N` only, so DIALS reads each module as its own panel. * Other: JPEG previews use a bundled encoder (libjpeg-turbo no longer needed), Rugnux prints how to cite it, and the release archives contain the HTML documentation. Reviewed-on: #92 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
83 lines
3.7 KiB
C++
83 lines
3.7 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <condition_variable>
|
|
#include <cstddef>
|
|
#include <mutex>
|
|
|
|
// The machine's physical memory in bytes - its total, not what is free at the moment. 0 where it
|
|
// cannot be read.
|
|
size_t get_host_total_memory();
|
|
|
|
// The host memory a run plans against: this fraction of the machine's physical memory, decided from
|
|
// the total, never from what happens to be free (as what runs on a GPU is decided on the card's total).
|
|
// The plan decides only what runs at the same time - two merges side by side or one after the other,
|
|
// how many large per-worker buffers are held at once - and never what is computed, so no result
|
|
// depends on it.
|
|
constexpr double HOST_MEMORY_BUDGET_FRACTION = 0.75;
|
|
size_t DefaultHostMemoryBudget();
|
|
|
|
// The share of the budget that the large per-worker buffers of parallel steps - a translation function's
|
|
// grids, a null replicate's model maps - may hold at once. The rest is what the run holds beside them:
|
|
// its reflections, its merge, the detector-sized arrays.
|
|
constexpr double HOST_WORKER_POOL_FRACTION = 0.5;
|
|
|
|
// One pool for those buffers, process-wide, held in one ledger with what the run holds beside them. The run
|
|
// says what it holds - its reflections, its merge engine, a validation's own grids - as Held bytes, and the
|
|
// workers may hold at once the pool's size or what the budget leaves beside the Held bytes, whichever is
|
|
// less: so the two together never claim more than the budget, however large the run's own part is. A worker
|
|
// reserves its bytes before it makes them and hands them back when it is done; one that would take the pool
|
|
// past that waits until others have handed theirs back - except that a reservation made while no worker holds
|
|
// one is always let through, however large. So how many such workers run at once is decided by their bytes,
|
|
// not by the thread count, and however the steps holding them nest or run side by side; what each worker
|
|
// computes, and where its result goes, does not change. A worker must not reserve again while it holds a
|
|
// reservation, and none may reserve on ParallelFor's pool: a holder's own parallel passes need those threads.
|
|
// Work spread over that pool is bounded by its count instead (Size() / bytes each).
|
|
class HostWorkerPool {
|
|
public:
|
|
static HostWorkerPool &Instance();
|
|
|
|
// The pool's size in bytes; 0 sets it back to HOST_WORKER_POOL_FRACTION of DefaultHostMemoryBudget().
|
|
void SetSize(size_t bytes);
|
|
// The budget the pool and the Held bytes share; 0 sets it back to DefaultHostMemoryBudget().
|
|
void SetBudget(size_t bytes);
|
|
// What the workers may hold at once now: the pool's size, or the budget less the Held bytes if less.
|
|
[[nodiscard]] size_t Size();
|
|
|
|
class Reservation {
|
|
public:
|
|
Reservation(HostWorkerPool &pool, size_t bytes);
|
|
~Reservation();
|
|
Reservation(const Reservation &) = delete;
|
|
Reservation &operator=(const Reservation &) = delete;
|
|
private:
|
|
HostWorkerPool &pool;
|
|
size_t bytes;
|
|
};
|
|
|
|
// Bytes the run holds beside the workers for as long as this lives; never waits.
|
|
class Held {
|
|
public:
|
|
Held(HostWorkerPool &pool, size_t bytes);
|
|
~Held();
|
|
Held(const Held &) = delete;
|
|
Held &operator=(const Held &) = delete;
|
|
void Set(size_t bytes); // what it holds from now on
|
|
private:
|
|
HostWorkerPool &pool;
|
|
size_t bytes;
|
|
};
|
|
|
|
private:
|
|
HostWorkerPool();
|
|
size_t available() const; // Size(), with the lock held
|
|
std::mutex m;
|
|
std::condition_variable cv;
|
|
size_t size = 0;
|
|
size_t budget = 0;
|
|
size_t held = 0;
|
|
size_t reserved = 0;
|
|
};
|