perf(parallel freq): remove mutexes in favor of fold for counters main
authorMA Beaudet <ma@beaudet.xyz>
Mon, 15 Nov 2021 10:09:04 +0000 (11:09 +0100)
committerMA Beaudet <ma@beaudet.xyz>
Mon, 15 Nov 2021 10:09:04 +0000 (11:09 +0100)
src/lib.rs

index 8be319c..10b64c6 100644 (file)
@@ -10,7 +10,6 @@ pub mod evaluator;
 use std::{
     collections::HashMap,
     convert::{TryFrom, TryInto},
-    sync::{Arc, Mutex},
     thread,
 };
 
@@ -340,42 +339,49 @@ pub fn deck_freq_evaluator(deck: &[u32]) -> [u32; 10] {
 }
 
 /// Concurrent evaluation of all five-card poker hands.
-///
-/// This implementation is actually slower in release builds.
-/// This is probably due to the Mutex wrap on `freq`.
 pub fn parallel_deck_freq_evaluator(deck: &[u32]) -> [u32; 10] {
+    use std::sync::{mpsc, Arc};
+
+    fn fold_counters(mut acc: [u32; 10], count: [u32; 10]) -> [u32; 10] {
+        count.iter().enumerate().for_each(|(k, v)| acc[k] += v);
+        acc
+    }
+
     let deck = Arc::new(Vec::from(deck));
-    let freq: Arc<Mutex<[u32; 10]>> = Arc::new(Mutex::new([0; 10]));
-    let thread_vec = (0..48).collect::<Vec<_>>();
-    let mut thread_handles = vec![];
-
-    for v in thread_vec.chunks(6) {
-        let v = v.to_owned();
-        let deck = deck.clone();
-        let freq = freq.clone();
-        thread_handles.push(thread::spawn(move || {
-            for a in v {
-                for b in a + 1..49 {
-                    for c in b + 1..50 {
-                        for d in c + 1..51 {
-                            for e in d + 1..52 {
-                                let i = eval_5hand(&[deck[a], deck[b], deck[c], deck[d], deck[e]]);
-                                let j = hand_rank(i);
-                                let mut freq = freq.lock().unwrap();
-                                freq[j as usize] += 1;
+
+    // tx goes out of scope before folding counters
+    // Otherwise you would need to explicitly call `drop(tx)`
+    let rx = {
+        let (tx, rx) = mpsc::channel();
+
+        let thread_vec: Vec<usize> = (0..48).into_iter().collect();
+        for v in thread_vec.chunks(6) {
+            let v = v.to_owned();
+            let deck = deck.clone();
+            let tx = tx.clone();
+            thread::spawn(move || {
+                let mut freq = [0; 10];
+                for a in v.clone() {
+                    for b in a + 1..49 {
+                        for c in b + 1..50 {
+                            for d in c + 1..51 {
+                                for e in d + 1..52 {
+                                    let i =
+                                        eval_5hand(&[deck[a], deck[b], deck[c], deck[d], deck[e]]);
+                                    let j = hand_rank(i);
+                                    freq[j as usize] += 1;
+                                }
                             }
                         }
                     }
                 }
-            }
-        }));
-    }
-
-    for handle in thread_handles {
-        handle.join().unwrap();
-    }
-    let freq = *freq.lock().unwrap();
-    freq
+                let _ = tx.send(freq);
+            });
+        }
+        rx
+    };
+    // drop(tx);
+    rx.iter().fold([0; 10], fold_counters)
 }
 
 /// Concurrent evaluation of all five-card poker hands using rayon crate.