multithreaded.rs 7.4 KB

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