upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormischief <mischief@offblast.org>2026-08-20 17:57:27 -0300
committerbitflash <admin@bitflash.network>2026-08-20 17:57:27 -0300
commit3a128281cd8fec1c698346191c240a771f80d72c (patch)
treeee4bd622380b62b81a64d791cbeac0eec5ab5422
parent721a7e46fadba3251564e2308b7b7eefcb2e9d36 (diff)
randomx: optional 2 MB large pages for the cache, dataset and scratchpads
RandomX reads its 256 MB cache, 2 GB dataset and per-VM scratchpads at random, so most accesses miss the TLB on 4 KB pages. Requesting 2 MB pages recovers roughly a tenth of the hash rate. Each allocation tries large pages first and falls back to normal pages when the pool is not reserved, so nothing regresses when hugepages are unavailable. -nolargepages opts out, and the diagnostics report which allocations got large pages.
-rw-r--r--src/main_gui.cpp7
-rw-r--r--src/net.cpp1
-rw-r--r--src/randomx_pow.cpp106
-rw-r--r--src/randomx_pow.h7
4 files changed, 110 insertions, 11 deletions
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index 0a961bf..a01bdee 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -127,6 +127,8 @@ static void PrintUsage()
127 printf(" /stratumbridgeport=N (default 3333; listen on 127.0.0.1)\n"); 127 printf(" /stratumbridgeport=N (default 3333; listen on 127.0.0.1)\n");
128 printf(" /solomine\n"); 128 printf(" /solomine\n");
129 printf(" /genproclimit=N (mining threads; 0 or absent = every core but one)\n"); 129 printf(" /genproclimit=N (mining threads; 0 or absent = every core but one)\n");
130 printf(" /nolargepages (do not ask for 2 MB pages for the RandomX\n");
131 printf(" cache, dataset and scratchpads)\n");
130 printf(" /checkblocks=N (blocks re-verified at startup, default 288, 0 = all)\n"); 132 printf(" /checkblocks=N (blocks re-verified at startup, default 288, 0 = all)\n");
131 printf("\n"); 133 printf("\n");
132 printf("Pool operator announcement:\n"); 134 printf("Pool operator announcement:\n");
@@ -230,6 +232,11 @@ static void ParseStartupArguments(int argc, char* argv[])
230 if (!strCheckBlocks.empty()) 232 if (!strCheckBlocks.empty())
231 nCheckBlocksOnLoad = atoi(strCheckBlocks.c_str()); 233 nCheckBlocksOnLoad = atoi(strCheckBlocks.c_str());
232 234
235 // Must be read before LoadBlockIndex(), which is what first calls
236 // RandomXInit() and fixes how the cache is allocated.
237 if (arg(argc,argv,"/nolargepages") || arg(argc,argv,"-nolargepages"))
238 fRandomXLargePages = false;
239
233 // /genproclimit=N -- threads to hash with. 0 or absent means automatic, 240 // /genproclimit=N -- threads to hash with. 0 or absent means automatic,
234 // which is every core but one. Named after Bitcoin's own option so it reads 241 // which is every core but one. Named after Bitcoin's own option so it reads
235 // familiarly to anyone who has run one of these before. 242 // familiarly to anyone who has run one of these before.
diff --git a/src/net.cpp b/src/net.cpp
index ecfd565..287a4bd 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -366,6 +366,7 @@ string GetDiagnosticsText()
366 nMining, 366 nMining,
367 (RandomXFastReady() ? 2080 : 256) + 2 * nMining).c_str() 367 (RandomXFastReady() ? 2080 : 256) + 2 * nMining).c_str()
368 : ", not mining"); 368 : ", not mining");
369 str += strprintf(" large pages %s\n", RandomXLargePagesStatus());
369 370
370 str += SockAccountingText(); 371 str += SockAccountingText();
371 372
diff --git a/src/randomx_pow.cpp b/src/randomx_pow.cpp
index 1d058d4..5872090 100644
--- a/src/randomx_pow.cpp
+++ b/src/randomx_pow.cpp
@@ -23,6 +23,39 @@ static std::mutex g_csInit;
23static bool g_fInit = false; 23static bool g_fInit = false;
24static bool g_fFast = false; 24static bool g_fFast = false;
25 25
26// RandomX reads a 256 MB cache, a 2 GB dataset and a 2 MB scratchpad per VM
27// at random, so most reads miss the TLB on 4 KB pages. 2 MB pages recover
28// roughly a tenth of the hash rate.
29//
30// The pool must be reserved first (vm.nr_hugepages), and RandomX returns NULL
31// instead of falling back, so each allocation below retries without the flag.
32bool fRandomXLargePages = true; // cleared by -nolargepages
33static bool g_fLargeCache = false;
34static bool g_fLargeDataset = false;
35static bool g_fLargeAny = false;
36
37static randomx_flags WithLargePages(randomx_flags f)
38{
39 return (randomx_flags)(f | RANDOMX_FLAG_LARGE_PAGES);
40}
41
42// Ask for a cache in large pages, then plain pages. Sets fLargeOut to say
43// which one answered.
44static randomx_cache* AllocCache(randomx_flags flags, bool& fLargeOut)
45{
46 fLargeOut = false;
47 if (fRandomXLargePages)
48 {
49 randomx_cache* c = randomx_alloc_cache(WithLargePages(flags));
50 if (c)
51 {
52 fLargeOut = true;
53 return c;
54 }
55 }
56 return randomx_alloc_cache(flags);
57}
58
26 59
27bool RandomXInit() 60bool RandomXInit()
28{ 61{
@@ -36,28 +69,56 @@ bool RandomXInit()
36 // Detect the best flags for this CPU (JIT, hardware AES, Argon2). 69 // Detect the best flags for this CPU (JIT, hardware AES, Argon2).
37 g_flags = randomx_get_flags(); 70 g_flags = randomx_get_flags();
38 71
39 g_cache = randomx_alloc_cache(g_flags); 72 g_cache = AllocCache(g_flags, g_fLargeCache);
40 if (!g_cache) 73 if (!g_cache)
41 { 74 {
42 // Try without JIT as a fallback 75 // Try without JIT as a fallback
43 g_flags = RANDOMX_FLAG_DEFAULT; 76 g_flags = RANDOMX_FLAG_DEFAULT;
44 g_cache = randomx_alloc_cache(g_flags); 77 g_cache = AllocCache(g_flags, g_fLargeCache);
45 if (!g_cache) 78 if (!g_cache)
46 return error("RandomX: failed to allocate cache (256 MB)"); 79 return error("RandomX: failed to allocate cache (256 MB)");
47 } 80 }
48 randomx_init_cache(g_cache, RANDOMX_KEY, strlen(RANDOMX_KEY)); 81 randomx_init_cache(g_cache, RANDOMX_KEY, strlen(RANDOMX_KEY));
49 82
50 // Verification VM in light mode (cache only) 83 // Verification VM in light mode (cache only). The scratchpad is only 2 MB
51 g_vmVerify = randomx_create_vm(g_flags, g_cache, NULL); 84 // and this VM is created once, but it hashes every block the node ever
85 // validates, so it gets the same treatment.
86 bool fLargeVerify = false;
87 if (fRandomXLargePages)
88 {
89 g_vmVerify = randomx_create_vm(WithLargePages(g_flags), g_cache, NULL);
90 fLargeVerify = (g_vmVerify != NULL);
91 }
92 if (!g_vmVerify)
93 g_vmVerify = randomx_create_vm(g_flags, g_cache, NULL);
52 if (!g_vmVerify) 94 if (!g_vmVerify)
53 return error("RandomX: failed to create verification VM"); 95 return error("RandomX: failed to create verification VM");
54 96
97 g_fLargeAny = g_fLargeCache || fLargeVerify;
55 g_fInit = true; 98 g_fInit = true;
56 printf("RandomX: initialized (cache 256 MB, flags=%d)\n", (int)g_flags); 99 printf("RandomX: initialized (cache 256 MB, flags=%d, large pages: %s)\n",
100 (int)g_flags, RandomXLargePagesStatus());
101 if (fRandomXLargePages && !g_fLargeCache)
102 printf("RandomX: large pages unavailable for the cache; "
103 "reserve them with sysctl vm.nr_hugepages=1280 for about 10%% more hash rate\n");
57 return true; 104 return true;
58} 105}
59 106
60 107
108const char* RandomXLargePagesStatus()
109{
110 if (!fRandomXLargePages)
111 return "off (-nolargepages)";
112 if (g_fLargeCache && g_fLargeDataset)
113 return "cache + dataset";
114 if (g_fLargeCache)
115 return "cache only";
116 if (g_fLargeDataset)
117 return "dataset only";
118 return g_fLargeAny ? "scratchpad only" : "unavailable";
119}
120
121
61// One dataset, once, no matter how many miners ask for it. 122// One dataset, once, no matter how many miners ask for it.
62// 123//
63// Every miner thread calls this at startup. The g_fFast guard below is only 124// Every miner thread calls this at startup. The g_fFast guard below is only
@@ -87,8 +148,18 @@ bool RandomXInitDataset(int nThreads)
87 if (g_fFast) 148 if (g_fFast)
88 return true; 149 return true;
89 150
90 randomx_flags fastFlags = (randomx_flags)(g_flags | RANDOMX_FLAG_FULL_MEM); 151 // Only RANDOMX_FLAG_LARGE_PAGES means anything to randomx_alloc_dataset;
91 g_dataset = randomx_alloc_dataset(fastFlags); 152 // the rest of g_flags belongs to the VMs that read it.
153 if (fRandomXLargePages)
154 {
155 g_dataset = randomx_alloc_dataset(WithLargePages(RANDOMX_FLAG_DEFAULT));
156 g_fLargeDataset = (g_dataset != NULL);
157 if (!g_dataset)
158 printf("RandomX: large pages unavailable for the 2 GB dataset; "
159 "reserve 1280 with sysctl vm.nr_hugepages for about 10%% more hash rate\n");
160 }
161 if (!g_dataset)
162 g_dataset = randomx_alloc_dataset(RANDOMX_FLAG_DEFAULT);
92 if (!g_dataset) 163 if (!g_dataset)
93 { 164 {
94 printf("RandomX: not enough memory for the 2 GB dataset, staying in light mode\n"); 165 printf("RandomX: not enough memory for the 2 GB dataset, staying in light mode\n");
@@ -142,13 +213,26 @@ void* RandomXCreateMinerVM()
142{ 213{
143 if (!g_fInit && !RandomXInit()) 214 if (!g_fInit && !RandomXInit())
144 return NULL; 215 return NULL;
216
217 randomx_flags flags = g_flags;
218 randomx_cache* cache = g_cache;
219 randomx_dataset* dataset = NULL;
145 if (g_fFast && g_dataset) 220 if (g_fFast && g_dataset)
146 { 221 {
147 randomx_flags fastFlags = (randomx_flags)(g_flags | RANDOMX_FLAG_FULL_MEM); 222 flags = (randomx_flags)(g_flags | RANDOMX_FLAG_FULL_MEM);
148 return randomx_create_vm(fastFlags, NULL, g_dataset); 223 cache = NULL;
224 dataset = g_dataset;
225 }
226
227 // Each thread gets its own 2 MB scratchpad, so the large-page pool can run
228 // dry partway through a fleet of miners. Threads that miss out still run.
229 if (fRandomXLargePages)
230 {
231 randomx_vm* vm = randomx_create_vm(WithLargePages(flags), cache, dataset);
232 if (vm)
233 return vm;
149 } 234 }
150 // Light mode: own VM sharing the cache 235 return randomx_create_vm(flags, cache, dataset);
151 return randomx_create_vm(g_flags, g_cache, NULL);
152} 236}
153 237
154 238
diff --git a/src/randomx_pow.h b/src/randomx_pow.h
index 0027571..355b708 100644
--- a/src/randomx_pow.h
+++ b/src/randomx_pow.h
@@ -41,4 +41,11 @@ uint256 RandomXHashWithVM(void* vm, const void* pHeader, size_t nSize);
41// True when the 2 GB dataset is ready (fast mode active). 41// True when the 2 GB dataset is ready (fast mode active).
42bool RandomXFastReady(); 42bool RandomXFastReady();
43 43
44// Ask for large (2 MB) pages for the cache, dataset and scratchpads. Set false
45// by -nolargepages. Must be set before the first RandomXInit().
46extern bool fRandomXLargePages;
47
48// Which allocations actually got large pages, for the status display.
49const char* RandomXLargePagesStatus();
50
44#endif 51#endif