|
| 1 | +use rand::distr::StandardUniform; |
| 2 | +use rand::Rng; |
| 3 | +use std::sync::{Arc, Mutex}; |
| 4 | +use std::time::Instant; |
| 5 | + |
| 6 | +#[derive(Debug, Default)] |
| 7 | +struct MonteCarloPiEstimate { |
| 8 | + points_in_circle: usize, |
| 9 | + points_total: usize, |
| 10 | +} |
| 11 | + |
| 12 | +fn get_random_points(num_samples: usize) -> Vec<(f32, f32)> { |
| 13 | + let mut rng = rand::rng(); |
| 14 | + let x: Vec<f32> = (&mut rng) |
| 15 | + .sample_iter(StandardUniform) |
| 16 | + .take(num_samples) |
| 17 | + .collect(); |
| 18 | + let y: Vec<f32> = (&mut rng) |
| 19 | + .sample_iter(StandardUniform) |
| 20 | + .take(num_samples) |
| 21 | + .collect(); |
| 22 | + |
| 23 | + x.into_iter().zip(y).collect() |
| 24 | +} |
| 25 | + |
| 26 | +fn is_in_circle(point: &(f32, f32)) -> bool { |
| 27 | + let (x, y) = point; |
| 28 | + x * x + y * y <= 1.0 |
| 29 | +} |
| 30 | + |
| 31 | +pub fn monte_carlo_pi(num_samples: usize, n_threads: Option<usize>) -> f32 { |
| 32 | + let n_threads = n_threads.unwrap_or_else(num_cpus::get); |
| 33 | + let samples_per_thread = num_samples.div_ceil(n_threads); |
| 34 | + |
| 35 | + println!("Using {} threads", n_threads); |
| 36 | + println!( |
| 37 | + "Using {} samples per thread, {} total samples", |
| 38 | + samples_per_thread, num_samples |
| 39 | + ); |
| 40 | + |
| 41 | + let mut thread_handles = Vec::with_capacity(n_threads); |
| 42 | + // Sure enough, we could simply compute an estimate for each thread and average them at the end. To show that mutexes allow sharing data between threads, we'll use a single estimate struct instead |
| 43 | + let estimates = Arc::new(Mutex::new(MonteCarloPiEstimate::default())); |
| 44 | + |
| 45 | + let start = Instant::now(); |
| 46 | + |
| 47 | + for thread_id in 0..n_threads { |
| 48 | + let estimates = estimates.clone(); |
| 49 | + let thread = std::thread::spawn(move || { |
| 50 | + println!("Thread {thread_id} started"); |
| 51 | + |
| 52 | + let points = get_random_points(samples_per_thread); |
| 53 | + let points_in_circle = points.iter().filter(|&p| is_in_circle(p)).count(); |
| 54 | + let mut estimates = estimates.lock().unwrap(); |
| 55 | + estimates.points_in_circle += points_in_circle; |
| 56 | + estimates.points_total += samples_per_thread; |
| 57 | + |
| 58 | + println!("Thread {thread_id} finished"); |
| 59 | + }); |
| 60 | + |
| 61 | + thread_handles.push(thread); |
| 62 | + } |
| 63 | + |
| 64 | + for thread_handle in thread_handles { |
| 65 | + thread_handle.join().unwrap(); |
| 66 | + } |
| 67 | + |
| 68 | + println!("Estimating pi took {:?}", start.elapsed()); |
| 69 | + let estimates = estimates.lock().unwrap(); |
| 70 | + println!("Simulation summary: {estimates:?}"); |
| 71 | + |
| 72 | + 4.0 * estimates.points_in_circle as f32 / estimates.points_total as f32 |
| 73 | +} |
0 commit comments