Files
Jungfraujoch/common/HostMemory.h
T
leonarski_fandClaude Opus 5.5 6f361ccde5 HostWorkerPool: half the budget, MR placements reserve 16 B per grid point, output unchanged
Without a GPU (macOS), rc176's worker pool - 1/3 of the 12.9 GB budget on a 16 GB Mac - let only two
molecular-replacement placements run at once, as each reserved 32 B per grid point (2.1 GB on 4nwv)
against a measured ~0.95 GB. MR rescoring went from 38.6 s to 69 s and 4nwv's wall time from 402 s
(budget off) to 444 s.

On the M6 Mac mini (16 GB), 4nwv, two runs each in alternating order on a quiet machine:
- old build 234774ff0: 418.4 s, 20.0-21.3 GiB peak footprint, 576-682k swapouts
- rc176 as shipped: 444.2 s, 14.5-15.2 GiB, 39-66k swapouts
- this change: 402.4 s, 15.5-15.8 GiB, 66-93k swapouts
- budget switched off: 401.7 s, 21.8-23.3 GiB, 268-433k swapouts
9zlo/9s02/9gjx: 71.9/79.0/82.8 s (old build 73.6/82.2/83.6), no swap. p.hkl byte-identical in every
run: the pool decides only what runs at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018My7tqt8a4LyKKjP3FhScT
2026-10-18 08:42:02 +02:00

61 lines
2.6 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. A worker reserves its bytes before it makes them and hands
// them back when it is done; one that would take the pool past its size waits until others have handed
// theirs back - except that a reservation made while nothing is reserved 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);
[[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;
};
private:
HostWorkerPool();
std::mutex m;
std::condition_variable cv;
size_t size = 0;
size_t reserved = 0;
};