benchmark.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
  2. use std::sync::Arc;
  3. use std::thread;
  4. use std::time::Instant;
  5. use randomx::*;
  6. const BLOCK_TEMPLATE: [u8; 76] = [
  7. 0x07, 0x07, 0xf7, 0xa4, 0xf0, 0xd6, 0x05, 0xb3, 0x03, 0x26, 0x08, 0x16, 0xba, 0x3f, 0x10, 0x90,
  8. 0x2e, 0x1a, 0x14, 0x5a, 0xc5, 0xfa, 0xd3, 0xaa, 0x3a, 0xf6, 0xea, 0x44, 0xc1, 0x18, 0x69, 0xdc,
  9. 0x4f, 0x85, 0x3f, 0x00, 0x2b, 0x2e, 0xea, 0x00, 0x00, 0x00, 0x00, 0x77, 0xb2, 0x06, 0xa0, 0x2c,
  10. 0xa5, 0xb1, 0xd4, 0xce, 0x6b, 0xbf, 0xdf, 0x0a, 0xca, 0xc3, 0x8b, 0xde, 0xd3, 0x4d, 0x2d, 0xcd,
  11. 0xee, 0xf9, 0x5c, 0xd2, 0x0c, 0xef, 0xc1, 0x2f, 0x61, 0xd5, 0x61, 0x09,
  12. ];
  13. struct AtomicHash {
  14. hash: [AtomicU64; 4],
  15. }
  16. impl AtomicHash {
  17. fn new() -> Self {
  18. Self {
  19. hash: [
  20. AtomicU64::new(0),
  21. AtomicU64::new(0),
  22. AtomicU64::new(0),
  23. AtomicU64::new(0),
  24. ],
  25. }
  26. }
  27. fn xor_with(&self, update: &[u64; 4]) {
  28. for (i, hash) in self.hash.iter().enumerate() {
  29. hash.fetch_xor(update[i], Ordering::SeqCst);
  30. }
  31. }
  32. fn print(&self) {
  33. for i in 0..4 {
  34. let h = self.hash[i].load(Ordering::SeqCst);
  35. let bytes = h.to_le_bytes();
  36. for byte in bytes {
  37. print!("{:02x}", byte);
  38. }
  39. }
  40. println!();
  41. }
  42. }
  43. fn mine<const BATCH: bool, const COMMIT: bool>(
  44. vm: &RandomXVM,
  45. atomic_nonce: &AtomicU32,
  46. result: &AtomicHash,
  47. nonces_count: u32,
  48. _thread: usize,
  49. _cpuid: i32,
  50. ) {
  51. let mut block_template = BLOCK_TEMPLATE;
  52. let mut nonce = atomic_nonce.fetch_add(1, Ordering::SeqCst);
  53. if BATCH {
  54. block_template[39..43].copy_from_slice(&nonce.to_le_bytes());
  55. vm.calculate_hash_first(&block_template).unwrap();
  56. }
  57. while nonce < nonces_count {
  58. if BATCH {
  59. nonce = atomic_nonce.fetch_add(1, Ordering::SeqCst);
  60. }
  61. block_template[39..43].copy_from_slice(&nonce.to_le_bytes());
  62. let mut hash = if BATCH {
  63. vm.calculate_hash_next(&block_template).unwrap()
  64. } else {
  65. vm.calculate_hash(&block_template).unwrap()
  66. };
  67. if COMMIT {
  68. let mut commitment = vec![0u8; RANDOMX_HASH_SIZE as usize];
  69. calculate_commitment(&block_template, &hash, &mut commitment).unwrap();
  70. hash = commitment;
  71. }
  72. let mut hash_u64 = [0u64; 4];
  73. for i in 0..4 {
  74. hash_u64[i] = u64::from_le_bytes([
  75. hash[i * 8],
  76. hash[i * 8 + 1],
  77. hash[i * 8 + 2],
  78. hash[i * 8 + 3],
  79. hash[i * 8 + 4],
  80. hash[i * 8 + 5],
  81. hash[i * 8 + 6],
  82. hash[i * 8 + 7],
  83. ]);
  84. }
  85. result.xor_with(&hash_u64);
  86. if !BATCH {
  87. nonce = atomic_nonce.fetch_add(1, Ordering::SeqCst);
  88. }
  89. }
  90. }
  91. fn print_usage(executable: &str) {
  92. println!("Usage: {} [OPTIONS]", executable);
  93. println!("Supported options:");
  94. println!(" --help shows this message");
  95. println!(" --mine mining mode: 2080 MiB");
  96. println!(" --verify verification mode: 256 MiB");
  97. println!(" --jit JIT compiled mode (default: interpreter)");
  98. println!(" --secure W^X policy for JIT pages (default: off)");
  99. println!(" --largePages use large pages (default: small pages)");
  100. println!(" --softAes use software AES (default: hardware AES)");
  101. println!(" --threads T use T threads (default: 1)");
  102. println!(" --affinity A thread affinity bitmask (default: 0)");
  103. println!(" --init Q initialize dataset with Q threads (default: 1)");
  104. println!(" --nonces N run N nonces (default: 1000)");
  105. println!(" --seed S seed for cache initialization (default: 0)");
  106. println!(" --ssse3 use optimized Argon2 for SSSE3 CPUs");
  107. println!(" --avx2 use optimized Argon2 for AVX2 CPUs");
  108. println!(" --auto select the best options for the current CPU");
  109. println!(" --noBatch calculate hashes one by one (default: batch)");
  110. println!(" --commit calculate commitments instead of hashes (default: hashes)");
  111. }
  112. fn has_arg(args: &[String], name: &str) -> bool {
  113. args.iter().any(|arg| arg == name)
  114. }
  115. fn get_int_arg(args: &[String], name: &str, default: i32) -> i32 {
  116. args.iter()
  117. .position(|arg| arg == name)
  118. .and_then(|pos| args.get(pos + 1))
  119. .and_then(|val| val.parse().ok())
  120. .unwrap_or(default)
  121. }
  122. fn get_u64_arg(args: &[String], name: &str, default: u64) -> u64 {
  123. args.iter()
  124. .position(|arg| arg == name)
  125. .and_then(|pos| args.get(pos + 1))
  126. .and_then(|val| val.parse().ok())
  127. .unwrap_or(default)
  128. }
  129. fn cpuid_from_mask(mask: u64, index: usize) -> i32 {
  130. let mut count = 0;
  131. for i in 0..64 {
  132. if (mask & (1u64 << i)) != 0 {
  133. if count == index {
  134. return i;
  135. }
  136. count += 1;
  137. }
  138. }
  139. -1
  140. }
  141. fn mask_to_string(mask: u64) -> String {
  142. let mut result = String::new();
  143. let mut first = true;
  144. for i in 0..64 {
  145. if (mask & (1u64 << i)) != 0 {
  146. if !first {
  147. result.push(',');
  148. }
  149. result.push_str(&i.to_string());
  150. first = false;
  151. }
  152. }
  153. result
  154. }
  155. fn main() {
  156. let args: Vec<String> = std::env::args().collect();
  157. let help = has_arg(&args, "--help");
  158. let mining_mode = has_arg(&args, "--mine");
  159. let verification_mode = has_arg(&args, "--verify");
  160. let soft_aes = has_arg(&args, "--softAes");
  161. let large_pages = has_arg(&args, "--largePages") || has_arg(&args, "--largepages");
  162. let jit = has_arg(&args, "--jit");
  163. let secure = has_arg(&args, "--secure");
  164. let ssse3 = has_arg(&args, "--ssse3");
  165. let avx2 = has_arg(&args, "--avx2");
  166. let auto_flags = has_arg(&args, "--auto");
  167. let no_batch = has_arg(&args, "--noBatch");
  168. let commit = has_arg(&args, "--commit");
  169. let thread_count = get_int_arg(&args, "--threads", 1) as usize;
  170. let thread_affinity = get_u64_arg(&args, "--affinity", 0);
  171. let nonces_count = get_int_arg(&args, "--nonces", 1000) as u32;
  172. let mut init_thread_count = get_int_arg(&args, "--init", 1) as usize;
  173. let seed_value = get_int_arg(&args, "--seed", 0);
  174. let seed = seed_value.to_le_bytes();
  175. println!("RandomX benchmark v1.2.1");
  176. if help {
  177. print_usage(&args[0]);
  178. return;
  179. }
  180. if !mining_mode && !verification_mode {
  181. println!("Please select either the fast mode (--mine) or the slow mode (--verify)");
  182. println!("Run '{}' --help' to see all supported options", args[0]);
  183. return;
  184. }
  185. let mut flags = if auto_flags {
  186. init_thread_count = thread::available_parallelism()
  187. .map(|n| n.get())
  188. .unwrap_or(1);
  189. RandomXFlags::get_recommended_flags()
  190. } else {
  191. let mut flags = RandomXFlags::DEFAULT;
  192. if ssse3 {
  193. flags |= RandomXFlags::ARGON2_SSSE3;
  194. }
  195. if avx2 {
  196. flags |= RandomXFlags::ARGON2_AVX2;
  197. }
  198. if !soft_aes {
  199. flags |= RandomXFlags::HARDAES;
  200. }
  201. if jit {
  202. flags |= RandomXFlags::JIT;
  203. }
  204. flags
  205. };
  206. if large_pages {
  207. flags |= RandomXFlags::LARGEPAGES;
  208. }
  209. if mining_mode {
  210. flags |= RandomXFlags::FULLMEM;
  211. }
  212. if secure {
  213. flags |= RandomXFlags::SECURE;
  214. }
  215. // Print configuration
  216. if flags.contains(RandomXFlags::ARGON2_AVX2) {
  217. println!(" - Argon2 implementation: AVX2");
  218. } else if flags.contains(RandomXFlags::ARGON2_SSSE3) {
  219. println!(" - Argon2 implementation: SSSE3");
  220. } else {
  221. println!(" - Argon2 implementation: reference");
  222. }
  223. if flags.contains(RandomXFlags::FULLMEM) {
  224. println!(" - full memory mode (2080 MiB)");
  225. } else {
  226. println!(" - light memory mode (256 MiB)");
  227. }
  228. if flags.contains(RandomXFlags::JIT) {
  229. print!(" - JIT compiled mode");
  230. if flags.contains(RandomXFlags::SECURE) {
  231. print!(" (secure)");
  232. }
  233. println!();
  234. } else {
  235. println!(" - interpreted mode");
  236. }
  237. if flags.contains(RandomXFlags::HARDAES) {
  238. println!(" - hardware AES mode");
  239. } else {
  240. println!(" - software AES mode");
  241. }
  242. if flags.contains(RandomXFlags::LARGEPAGES) {
  243. println!(" - large pages mode");
  244. } else {
  245. println!(" - small pages mode");
  246. }
  247. if thread_affinity != 0 {
  248. println!(" - thread affinity ({})", mask_to_string(thread_affinity));
  249. }
  250. if no_batch {
  251. if commit {
  252. println!(" - hash commitments");
  253. }
  254. } else if commit {
  255. println!(" - hash commitments");
  256. } else {
  257. println!(" - batch mode");
  258. }
  259. print!("Initializing");
  260. if mining_mode {
  261. print!(
  262. " ({} thread{})",
  263. init_thread_count,
  264. if init_thread_count > 1 { "s" } else { "" }
  265. );
  266. }
  267. println!(" ...");
  268. let start = Instant::now();
  269. let cache = RandomXCache::new(flags, &seed).unwrap();
  270. let dataset = if mining_mode {
  271. let dataset_item_count = RandomXDataset::count().unwrap();
  272. let dataset = RandomXDataset::new(flags, cache.clone(), dataset_item_count).unwrap();
  273. if init_thread_count > 1 {
  274. let per_thread = dataset_item_count / init_thread_count as u32;
  275. let remainder = dataset_item_count % init_thread_count as u32;
  276. let mut handles = vec![];
  277. for i in 0..init_thread_count {
  278. let dataset_clone = dataset.clone();
  279. let start_item = i as u32 * per_thread;
  280. let count = per_thread
  281. + if i == init_thread_count - 1 {
  282. remainder
  283. } else {
  284. 0
  285. };
  286. let handle = thread::spawn(move || {
  287. dataset_clone.init(start_item, count);
  288. });
  289. handles.push(handle);
  290. }
  291. for handle in handles {
  292. handle.join().unwrap();
  293. }
  294. } else {
  295. dataset.init(0, dataset_item_count);
  296. }
  297. Some(dataset)
  298. } else {
  299. None
  300. };
  301. let elapsed = start.elapsed();
  302. println!("Memory initialized in {:.3} s", elapsed.as_secs_f64());
  303. println!("Initializing {} virtual machine(s) ...", thread_count);
  304. let mut vms = Vec::new();
  305. for _ in 0..thread_count {
  306. let vm = if mining_mode {
  307. RandomXVM::new(flags, None, dataset.clone()).unwrap()
  308. } else {
  309. RandomXVM::new(flags, Some(cache.clone()), None).unwrap()
  310. };
  311. vms.push(Arc::new(vm));
  312. }
  313. println!("Running benchmark ({} nonces) ...", nonces_count);
  314. let atomic_nonce = Arc::new(AtomicU32::new(0));
  315. let result = Arc::new(AtomicHash::new());
  316. let start = Instant::now();
  317. if thread_count > 1 {
  318. let mut handles = vec![];
  319. for (i, vm) in vms.iter().enumerate() {
  320. let vm = vm.clone();
  321. let atomic_nonce = atomic_nonce.clone();
  322. let result = result.clone();
  323. let cpuid = if thread_affinity != 0 {
  324. cpuid_from_mask(thread_affinity, i)
  325. } else {
  326. -1
  327. };
  328. let handle = thread::spawn(move || match (no_batch, commit) {
  329. (false, false) => {
  330. mine::<true, false>(&vm, &atomic_nonce, &result, nonces_count, i, cpuid)
  331. }
  332. (false, true) => {
  333. mine::<false, true>(&vm, &atomic_nonce, &result, nonces_count, i, cpuid)
  334. }
  335. (true, false) => {
  336. mine::<false, false>(&vm, &atomic_nonce, &result, nonces_count, i, cpuid)
  337. }
  338. (true, true) => {
  339. mine::<false, true>(&vm, &atomic_nonce, &result, nonces_count, i, cpuid)
  340. }
  341. });
  342. handles.push(handle);
  343. }
  344. for handle in handles {
  345. handle.join().unwrap();
  346. }
  347. } else {
  348. match (no_batch, commit) {
  349. (false, false) => {
  350. mine::<true, false>(&vms[0], &atomic_nonce, &result, nonces_count, 0, -1)
  351. }
  352. (false, true) => {
  353. mine::<false, true>(&vms[0], &atomic_nonce, &result, nonces_count, 0, -1)
  354. }
  355. (true, false) => {
  356. mine::<false, false>(&vms[0], &atomic_nonce, &result, nonces_count, 0, -1)
  357. }
  358. (true, true) => {
  359. mine::<false, true>(&vms[0], &atomic_nonce, &result, nonces_count, 0, -1)
  360. }
  361. }
  362. }
  363. let elapsed = start.elapsed();
  364. print!("Calculated result: ");
  365. result.print();
  366. if nonces_count == 1000 && seed_value == 0 && !commit {
  367. println!(
  368. "Reference result: 10b649a3f15c7c7f88277812f2e74b337a0f20ce909af09199cccb960771cfa1"
  369. );
  370. }
  371. if !mining_mode {
  372. println!(
  373. "Performance: {:.3} ms per hash",
  374. elapsed.as_secs_f64() * 1000.0 / nonces_count as f64
  375. );
  376. } else {
  377. println!(
  378. "Performance: {:.2} hashes per second",
  379. nonces_count as f64 / elapsed.as_secs_f64()
  380. );
  381. }
  382. }