upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBitflash <btf01@proton.me>2026-07-31 11:27:50 -0300
committerGitHub <noreply@github.com>2026-07-31 11:27:50 -0300
commitf05274afd0becc7311fa69aa60b0215f300e5f28 (patch)
tree85f5842158688f59bfe88e3349866687ce0e5551
parentcca4e0c38595653846d9907650870df8c0768d77 (diff)
randomx: allocate the dataset once, not once per miner thread (#68)
Every miner thread calls RandomXInitDataset at startup. Its only guard is g_fFast, which is set at the very end -- after the ~2 GB dataset has been filled, which takes tens of seconds. So all of them sailed past the guard together and each allocated its own dataset. Measured in production on a 32-core machine running 31 miner threads: the log says "initializing ~2 GB dataset" 34 times, and the process holds 65,127 MB of private pages while its own startup line promises 2142 MB "(~2080 MB shared RandomX dataset + ~2 MB per thread)". The word shared was not true. A 4-core machine showed the same thing at its own scale: 4 allocations for 3 threads. The waste was the mild half. g_dataset is assigned the moment memory is allocated, before it holds anything, so the last thread to allocate swung the pointer out from under threads still filling their own. A miner could then hash against a dataset another thread had not finished writing, and proof of work computed that way is not reproducible by any other node -- the block would be found invalid by everyone, including this node's own verifier, which uses the cache rather than the dataset. This only became reachable with multi-threaded mining (#52); with one thread there was nobody to race. Holding the lock across the whole initialisation is deliberate: a thread arriving mid-init has nothing useful to do until the dataset exists, and blocking is how it waits for exactly that. Verified with three mining threads: one "initializing ~2 GB dataset", one "dataset ready", and 2362 MB of private memory against 2086 MB advertised -- the difference being the 256 MB cache. Before, three threads meant three datasets.
-rw-r--r--src/randomx_pow.cpp23
1 files changed, 23 insertions, 0 deletions
diff --git a/src/randomx_pow.cpp b/src/randomx_pow.cpp
index 17bf455..d617ba2 100644
--- a/src/randomx_pow.cpp
+++ b/src/randomx_pow.cpp
@@ -7,6 +7,7 @@
7#include <randomx.h> 7#include <randomx.h>
8#include <thread> 8#include <thread>
9#include <vector> 9#include <vector>
10#include <mutex>
10 11
11// Fixed key (seed) that determines the RandomX cache/dataset. Keeping it 12// Fixed key (seed) that determines the RandomX cache/dataset. Keeping it
12// constant avoids per-epoch "reseed" logic; for the CPU+RAM fairness goal 13// constant avoids per-epoch "reseed" logic; for the CPU+RAM fairness goal
@@ -52,10 +53,32 @@ bool RandomXInit()
52} 53}
53 54
54 55
56// One dataset, once, no matter how many miners ask for it.
57//
58// Every miner thread calls this at startup. The g_fFast guard below is only
59// set at the very end, after the ~2 GB dataset has been filled, which takes
60// tens of seconds -- so without this lock all of them sail past the guard
61// together and each allocates its own dataset. Measured in production: a
62// 32-core machine logged "initializing ~2 GB dataset" 34 times and committed
63// 65 GB of private pages against a startup message promising 2142 MB.
64//
65// The waste was the mild half. g_dataset is assigned the moment the memory is
66// allocated, before it holds anything, so the last thread to allocate would
67// swing the pointer out from under the threads still filling their own. A
68// miner could then hash against a dataset another thread had not finished
69// writing, producing proof of work that no other node can reproduce.
70//
71// Holding the lock across the whole initialisation is deliberate: a thread
72// that arrives mid-init has nothing useful to do until the dataset exists,
73// and blocking is how it waits for exactly that.
74static std::mutex g_csDataset;
75
55bool RandomXInitDataset(int nThreads) 76bool RandomXInitDataset(int nThreads)
56{ 77{
57 if (!g_fInit && !RandomXInit()) 78 if (!g_fInit && !RandomXInit())
58 return false; 79 return false;
80
81 std::lock_guard<std::mutex> lock(g_csDataset);
59 if (g_fFast) 82 if (g_fFast)
60 return true; 83 return true;
61 84