upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/randomx_pow.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/randomx_pow.cpp')
-rw-r--r--src/randomx_pow.cpp150
1 files changed, 133 insertions, 17 deletions
diff --git a/src/randomx_pow.cpp b/src/randomx_pow.cpp
index d617ba2..a509543 100644
--- a/src/randomx_pow.cpp
+++ b/src/randomx_pow.cpp
@@ -8,6 +8,7 @@
8#include <thread> 8#include <thread>
9#include <vector> 9#include <vector>
10#include <mutex> 10#include <mutex>
11#include <system_error>
11 12
12// Fixed key (seed) that determines the RandomX cache/dataset. Keeping it 13// Fixed key (seed) that determines the RandomX cache/dataset. Keeping it
13// constant avoids per-epoch "reseed" logic; for the CPU+RAM fairness goal 14// constant avoids per-epoch "reseed" logic; for the CPU+RAM fairness goal
@@ -19,40 +20,117 @@ static randomx_cache* g_cache = NULL;
19static randomx_dataset* g_dataset = NULL; 20static randomx_dataset* g_dataset = NULL;
20static randomx_vm* g_vmVerify = NULL; 21static randomx_vm* g_vmVerify = NULL;
21static CCriticalSection g_csVerify; 22static CCriticalSection g_csVerify;
23static std::mutex g_csInit;
22static bool g_fInit = false; 24static bool g_fInit = false;
23static bool g_fFast = false; 25static bool g_fFast = false;
24 26
27// RandomX reads a 256 MB cache, a 2 GB dataset and a 2 MB scratchpad per VM
28// at random, so most reads miss the TLB on 4 KB pages. 2 MB pages recover
29// roughly a tenth of the hash rate.
30//
31// The pool must be reserved first (vm.nr_hugepages), and RandomX returns NULL
32// instead of falling back, so each allocation below retries without the flag.
33bool fRandomXLargePages = true; // cleared by -nolargepages
34static bool g_fLargeCache = false;
35static bool g_fLargeDataset = false;
36static bool g_fLargeAny = false;
37
38// How to reserve the large-page pool, for the hint printed when a large-page
39// allocation falls back to normal pages. The mechanism is OS-specific.
40static const char* LargePagesHint()
41{
42#ifdef _WIN32
43 return "enable the \"Lock pages in memory\" privilege and run elevated";
44#else
45 return "reserve them with: sysctl vm.nr_hugepages=1280";
46#endif
47}
48
49static randomx_flags WithLargePages(randomx_flags f)
50{
51 return (randomx_flags)(f | RANDOMX_FLAG_LARGE_PAGES);
52}
53
54// Ask for a cache in large pages, then plain pages. Sets fLargeOut to say
55// which one answered.
56static randomx_cache* AllocCache(randomx_flags flags, bool& fLargeOut)
57{
58 fLargeOut = false;
59 if (fRandomXLargePages)
60 {
61 randomx_cache* c = randomx_alloc_cache(WithLargePages(flags));
62 if (c)
63 {
64 fLargeOut = true;
65 return c;
66 }
67 }
68 return randomx_alloc_cache(flags);
69}
70
25 71
26bool RandomXInit() 72bool RandomXInit()
27{ 73{
28 if (g_fInit) 74 if (g_fInit)
29 return true; 75 return true;
30 76
77 std::lock_guard<std::mutex> lock(g_csInit);
78 if (g_fInit)
79 return true;
80
31 // Detect the best flags for this CPU (JIT, hardware AES, Argon2). 81 // Detect the best flags for this CPU (JIT, hardware AES, Argon2).
32 g_flags = randomx_get_flags(); 82 g_flags = randomx_get_flags();
33 83
34 g_cache = randomx_alloc_cache(g_flags); 84 g_cache = AllocCache(g_flags, g_fLargeCache);
35 if (!g_cache) 85 if (!g_cache)
36 { 86 {
37 // Try without JIT as a fallback 87 // Try without JIT as a fallback
38 g_flags = RANDOMX_FLAG_DEFAULT; 88 g_flags = RANDOMX_FLAG_DEFAULT;
39 g_cache = randomx_alloc_cache(g_flags); 89 g_cache = AllocCache(g_flags, g_fLargeCache);
40 if (!g_cache) 90 if (!g_cache)
41 return error("RandomX: failed to allocate cache (256 MB)"); 91 return error("RandomX: failed to allocate cache (256 MB)");
42 } 92 }
43 randomx_init_cache(g_cache, RANDOMX_KEY, strlen(RANDOMX_KEY)); 93 randomx_init_cache(g_cache, RANDOMX_KEY, strlen(RANDOMX_KEY));
44 94
45 // Verification VM in light mode (cache only) 95 // Verification VM in light mode (cache only). The scratchpad is only 2 MB
46 g_vmVerify = randomx_create_vm(g_flags, g_cache, NULL); 96 // and this VM is created once, but it hashes every block the node ever
97 // validates, so it gets the same treatment.
98 bool fLargeVerify = false;
99 if (fRandomXLargePages)
100 {
101 g_vmVerify = randomx_create_vm(WithLargePages(g_flags), g_cache, NULL);
102 fLargeVerify = (g_vmVerify != NULL);
103 }
104 if (!g_vmVerify)
105 g_vmVerify = randomx_create_vm(g_flags, g_cache, NULL);
47 if (!g_vmVerify) 106 if (!g_vmVerify)
48 return error("RandomX: failed to create verification VM"); 107 return error("RandomX: failed to create verification VM");
49 108
109 g_fLargeAny = g_fLargeCache || fLargeVerify;
50 g_fInit = true; 110 g_fInit = true;
51 printf("RandomX: initialized (cache 256 MB, flags=%d)\n", (int)g_flags); 111 printf("RandomX: initialized (cache 256 MB, flags=%d, large pages: %s)\n",
112 (int)g_flags, RandomXLargePagesStatus());
113 if (fRandomXLargePages && !g_fLargeCache)
114 printf("RandomX: large pages unavailable for the cache; %s "
115 "for about 10%% more hash rate\n", LargePagesHint());
52 return true; 116 return true;
53} 117}
54 118
55 119
120const char* RandomXLargePagesStatus()
121{
122 if (!fRandomXLargePages)
123 return "off (-nolargepages)";
124 if (g_fLargeCache && g_fLargeDataset)
125 return "cache + dataset";
126 if (g_fLargeCache)
127 return "cache only";
128 if (g_fLargeDataset)
129 return "dataset only";
130 return g_fLargeAny ? "scratchpad only" : "unavailable";
131}
132
133
56// One dataset, once, no matter how many miners ask for it. 134// One dataset, once, no matter how many miners ask for it.
57// 135//
58// Every miner thread calls this at startup. The g_fFast guard below is only 136// Every miner thread calls this at startup. The g_fFast guard below is only
@@ -82,8 +160,18 @@ bool RandomXInitDataset(int nThreads)
82 if (g_fFast) 160 if (g_fFast)
83 return true; 161 return true;
84 162
85 randomx_flags fastFlags = (randomx_flags)(g_flags | RANDOMX_FLAG_FULL_MEM); 163 // Only RANDOMX_FLAG_LARGE_PAGES means anything to randomx_alloc_dataset;
86 g_dataset = randomx_alloc_dataset(fastFlags); 164 // the rest of g_flags belongs to the VMs that read it.
165 if (fRandomXLargePages)
166 {
167 g_dataset = randomx_alloc_dataset(WithLargePages(RANDOMX_FLAG_DEFAULT));
168 g_fLargeDataset = (g_dataset != NULL);
169 if (!g_dataset)
170 printf("RandomX: large pages unavailable for the 2 GB dataset; %s "
171 "for about 10%% more hash rate\n", LargePagesHint());
172 }
173 if (!g_dataset)
174 g_dataset = randomx_alloc_dataset(RANDOMX_FLAG_DEFAULT);
87 if (!g_dataset) 175 if (!g_dataset)
88 { 176 {
89 printf("RandomX: not enough memory for the 2 GB dataset, staying in light mode\n"); 177 printf("RandomX: not enough memory for the 2 GB dataset, staying in light mode\n");
@@ -96,13 +184,28 @@ bool RandomXInitDataset(int nThreads)
96 184
97 std::vector<std::thread> workers; 185 std::vector<std::thread> workers;
98 unsigned long per = total / nThreads; 186 unsigned long per = total / nThreads;
99 for (int i = 0; i < nThreads; i++) 187 try
188 {
189 for (int i = 0; i < nThreads; i++)
190 {
191 unsigned long start = i * per;
192 unsigned long count = (i == nThreads - 1) ? (total - start) : per;
193 workers.emplace_back([start, count]() {
194 randomx_init_dataset(g_dataset, g_cache, start, count);
195 });
196 }
197 }
198 catch (const std::system_error& e)
100 { 199 {
101 unsigned long start = i * per; 200 // Same refusal as the allocation failure above: light mode is slow
102 unsigned long count = (i == nThreads - 1) ? (total - start) : per; 201 // but alive, std::terminate is neither. Join the workers that did
103 workers.emplace_back([start, count]() { 202 // start before releasing the dataset out from under them.
104 randomx_init_dataset(g_dataset, g_cache, start, count); 203 for (auto& w : workers) w.join();
105 }); 204 randomx_release_dataset(g_dataset);
205 g_dataset = NULL;
206 printf("RandomX: cannot spawn dataset init threads (%s), staying in light mode\n",
207 e.what());
208 return false;
106 } 209 }
107 for (auto& w : workers) w.join(); 210 for (auto& w : workers) w.join();
108 211
@@ -137,13 +240,26 @@ void* RandomXCreateMinerVM()
137{ 240{
138 if (!g_fInit && !RandomXInit()) 241 if (!g_fInit && !RandomXInit())
139 return NULL; 242 return NULL;
243
244 randomx_flags flags = g_flags;
245 randomx_cache* cache = g_cache;
246 randomx_dataset* dataset = NULL;
140 if (g_fFast && g_dataset) 247 if (g_fFast && g_dataset)
141 { 248 {
142 randomx_flags fastFlags = (randomx_flags)(g_flags | RANDOMX_FLAG_FULL_MEM); 249 flags = (randomx_flags)(g_flags | RANDOMX_FLAG_FULL_MEM);
143 return randomx_create_vm(fastFlags, NULL, g_dataset); 250 cache = NULL;
251 dataset = g_dataset;
252 }
253
254 // Each thread gets its own 2 MB scratchpad, so the large-page pool can run
255 // dry partway through a fleet of miners. Threads that miss out still run.
256 if (fRandomXLargePages)
257 {
258 randomx_vm* vm = randomx_create_vm(WithLargePages(flags), cache, dataset);
259 if (vm)
260 return vm;
144 } 261 }
145 // Light mode: own VM sharing the cache 262 return randomx_create_vm(flags, cache, dataset);
146 return randomx_create_vm(g_flags, g_cache, NULL);
147} 263}
148 264
149 265