multithreaded.rs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. //! randomx example that calculates many hashes using multiple threads
  2. use std::collections::HashMap;
  3. use std::sync::{Arc, RwLock};
  4. use std::thread;
  5. use std::time::Instant;
  6. use anyhow::Result;
  7. use randomx::*;
  8. #[derive(Clone)]
  9. pub struct RandomXVMInstance {
  10. instance: Arc<RwLock<RandomXVM>>,
  11. }
  12. unsafe impl Send for RandomXVMInstance {}
  13. unsafe impl Sync for RandomXVMInstance {}
  14. impl RandomXVMInstance {
  15. fn create(
  16. key: &[u8],
  17. flags: RandomXFlags,
  18. cache: Option<RandomXCache>,
  19. dataset: Option<RandomXDataset>,
  20. ) -> Result<Self> {
  21. // Note: Memory requirement per VM in light mode is 256MB
  22. // Note: RandomXFlags::FULLMEM and RandomXFlags::LARGEPAGES are incompatible
  23. // with light mode. These are not set by RandomX automatically even in fast mode.
  24. let (flags, cache) = match cache {
  25. Some(c) => (flags, c),
  26. None => match RandomXCache::new(flags, key) {
  27. Ok(cache) => (flags, cache),
  28. Err(_) => {
  29. // Fallback to default flags
  30. let flags = RandomXFlags::DEFAULT;
  31. let cache = RandomXCache::new(flags, key)?;
  32. (flags, cache)
  33. }
  34. },
  35. };
  36. let vm = RandomXVM::new(flags, Some(cache), dataset)?;
  37. Ok(Self {
  38. instance: Arc::new(RwLock::new(vm)),
  39. })
  40. }
  41. /// Calculate the RandomX mining hash
  42. pub fn calculate_hash(&self, input: &[u8]) -> Result<Vec<u8>> {
  43. let lock = self.instance.write().unwrap();
  44. Ok(lock.calculate_hash(input)?)
  45. }
  46. }
  47. #[derive(Clone, Debug)]
  48. pub struct RandomXFactory {
  49. inner: Arc<RwLock<RandomXFactoryInner>>,
  50. }
  51. impl Default for RandomXFactory {
  52. fn default() -> Self {
  53. Self::new(2)
  54. }
  55. }
  56. impl RandomXFactory {
  57. /// Create a new RandomX factory with the specified maximum number of VMs
  58. pub fn new(max_vms: usize) -> Self {
  59. Self {
  60. inner: Arc::new(RwLock::new(RandomXFactoryInner::new(max_vms))),
  61. }
  62. }
  63. pub fn new_with_flags(max_vms: usize, flags: RandomXFlags) -> Self {
  64. Self {
  65. inner: Arc::new(RwLock::new(RandomXFactoryInner::new_with_flags(
  66. max_vms, flags,
  67. ))),
  68. }
  69. }
  70. /// Create a new RandomX VM instance with the specified key
  71. pub fn create(
  72. &self,
  73. key: &[u8],
  74. cache: Option<RandomXCache>,
  75. dataset: Option<RandomXDataset>,
  76. ) -> Result<RandomXVMInstance> {
  77. let res;
  78. {
  79. let mut inner = self.inner.write().unwrap();
  80. res = inner.create(key, cache, dataset)?;
  81. }
  82. Ok(res)
  83. }
  84. /// Get the number of VMs currently allocated
  85. pub fn get_count(&self) -> Result<usize> {
  86. let inner = self.inner.read().unwrap();
  87. Ok(inner.get_count())
  88. }
  89. /// Get the flags used to create the VMs
  90. pub fn get_flags(&self) -> Result<RandomXFlags> {
  91. let inner = self.inner.read().unwrap();
  92. Ok(inner.get_flags())
  93. }
  94. }
  95. struct RandomXFactoryInner {
  96. flags: RandomXFlags,
  97. vms: HashMap<Vec<u8>, (Instant, RandomXVMInstance)>,
  98. max_vms: usize,
  99. }
  100. impl std::fmt::Debug for RandomXFactoryInner {
  101. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  102. f.debug_struct("RandomXFactory")
  103. .field("flags", &self.flags)
  104. .field("max_vms", &self.max_vms)
  105. .finish()
  106. }
  107. }
  108. impl RandomXFactoryInner {
  109. fn new(max_vms: usize) -> Self {
  110. let flags = RandomXFlags::get_recommended_flags();
  111. Self {
  112. flags,
  113. vms: Default::default(),
  114. max_vms,
  115. }
  116. }
  117. fn new_with_flags(max_vms: usize, flags: RandomXFlags) -> Self {
  118. Self {
  119. flags,
  120. vms: Default::default(),
  121. max_vms,
  122. }
  123. }
  124. fn create(
  125. &mut self,
  126. key: &[u8],
  127. cache: Option<RandomXCache>,
  128. dataset: Option<RandomXDataset>,
  129. ) -> Result<RandomXVMInstance> {
  130. if let Some(entry) = self.vms.get_mut(key) {
  131. let vm = entry.1.clone();
  132. entry.0 = Instant::now();
  133. return Ok(vm);
  134. }
  135. if self.vms.len() >= self.max_vms {
  136. if let Some(oldest_key) = self
  137. .vms
  138. .iter()
  139. .min_by_key(|(_, (i, _))| *i)
  140. .map(|(k, _)| k.clone())
  141. {
  142. self.vms.remove(&oldest_key);
  143. }
  144. }
  145. let vm = RandomXVMInstance::create(key, self.flags, cache, dataset)?;
  146. self.vms
  147. .insert(Vec::from(key), (Instant::now(), vm.clone()));
  148. Ok(vm)
  149. }
  150. /// Get the number of VMs currently allocated
  151. fn get_count(&self) -> usize {
  152. self.vms.len()
  153. }
  154. /// Get the flags used to create the VMs
  155. fn get_flags(&self) -> RandomXFlags {
  156. self.flags
  157. }
  158. }
  159. fn main() {
  160. const NUM_THREADS: u32 = 8;
  161. // number of hashes to perform in each thread, not the total.
  162. const NUM_HASHES: u32 = 100;
  163. // Try adding `| RandomXFlags::LARGEPAGES`.
  164. let mut flags = RandomXFlags::get_recommended_flags() | RandomXFlags::FULLMEM;
  165. if is_x86_feature_detected!("avx2") {
  166. flags |= RandomXFlags::ARGON2_AVX2;
  167. } else if is_x86_feature_detected!("ssse3") {
  168. flags |= RandomXFlags::ARGON2_SSSE3;
  169. }
  170. let factory = RandomXFactory::new_with_flags(1, flags);
  171. let key = b"key";
  172. let start = Instant::now();
  173. let cache = RandomXCache::new(flags, &key[..]).unwrap();
  174. let dataset = RandomXDataset::new(flags, cache, 0).unwrap();
  175. println!("Initialized RandomX dataset in {:?}", start.elapsed());
  176. let mut handles = Vec::new();
  177. let start = Instant::now();
  178. for i in 0..NUM_THREADS {
  179. let factory = factory.clone();
  180. let dataset = dataset.clone();
  181. handles.push(thread::spawn(move || {
  182. let key = b"key";
  183. let vm = factory.create(&key[..], None, Some(dataset)).unwrap();
  184. println!("Created VM #{}", i);
  185. let mut nonce: u32 = i;
  186. for _ in 0..NUM_HASHES {
  187. let _ = vm.calculate_hash(&nonce.to_be_bytes()[..]);
  188. //println!("VM #{} calculated hash with nonce {}", i, nonce);
  189. // e.g. thread 0 will use nonces 0, 8, 16, ...
  190. // and thread 1 will use nonces 1, 9, 17, ...
  191. nonce += NUM_THREADS;
  192. }
  193. }));
  194. }
  195. for handle in handles {
  196. let _ = handle.join();
  197. }
  198. println!(
  199. "Completed {} hashes in {}ms",
  200. NUM_THREADS * NUM_HASHES,
  201. start.elapsed().as_millis()
  202. );
  203. }