Skip to content

Commit 1b2e14a

Browse files
committed
Simplify threading by collecting values from joined threads
1 parent 5792f82 commit 1b2e14a

File tree

11 files changed

+107
-162
lines changed

11 files changed

+107
-162
lines changed

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
8080
| 3 | [Mull It Over](https://adventofcode.com/2024/day/3) | [Source](src/year2024/day03.rs) | 8 |
8181
| 4 | [Ceres Search](https://adventofcode.com/2024/day/4) | [Source](src/year2024/day04.rs) | 77 |
8282
| 5 | [Print Queue](https://adventofcode.com/2024/day/5) | [Source](src/year2024/day05.rs) | 18 |
83-
| 6 | [Guard Gallivant](https://adventofcode.com/2024/day/6) | [Source](src/year2024/day06.rs) | 331 |
83+
| 6 | [Guard Gallivant](https://adventofcode.com/2024/day/6) | [Source](src/year2024/day06.rs) | 439 |
8484
| 7 | [Bridge Repair](https://adventofcode.com/2024/day/7) | [Source](src/year2024/day07.rs) | 136 |
8585
| 8 | [Resonant Collinearity](https://adventofcode.com/2024/day/8) | [Source](src/year2024/day08.rs) | 8 |
8686
| 9 | [Disk Fragmenter](https://adventofcode.com/2024/day/9) | [Source](src/year2024/day09.rs) | 106 |
@@ -94,9 +94,9 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
9494
| 17 | [Chronospatial Computer](https://adventofcode.com/2024/day/17) | [Source](src/year2024/day17.rs) | 2 |
9595
| 18 | [RAM Run](https://adventofcode.com/2024/day/18) | [Source](src/year2024/day18.rs) | 42 |
9696
| 19 | [Linen Layout](https://adventofcode.com/2024/day/19) | [Source](src/year2024/day19.rs) | 110 |
97-
| 20 | [Race Condition](https://adventofcode.com/2024/day/20) | [Source](src/year2024/day20.rs) | 1038 |
97+
| 20 | [Race Condition](https://adventofcode.com/2024/day/20) | [Source](src/year2024/day20.rs) | 1113 |
9898
| 21 | [Keypad Conundrum](https://adventofcode.com/2024/day/21) | [Source](src/year2024/day21.rs) | 19 |
99-
| 22 | [Monkey Market](https://adventofcode.com/2024/day/22) | [Source](src/year2024/day22.rs) | 727 |
99+
| 22 | [Monkey Market](https://adventofcode.com/2024/day/22) | [Source](src/year2024/day22.rs) | 688 |
100100
| 23 | [LAN Party](https://adventofcode.com/2024/day/23) | [Source](src/year2024/day23.rs) | 43 |
101101
| 24 | [Crossed Wires](https://adventofcode.com/2024/day/24) | [Source](src/year2024/day24.rs) | 23 |
102102
| 25 | [Code Chronicle](https://adventofcode.com/2024/day/25) | [Source](src/year2024/day25.rs) | 8 |
@@ -118,7 +118,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
118118
| 9 | [Mirage Maintenance](https://adventofcode.com/2023/day/9) | [Source](src/year2023/day09.rs) | 18 |
119119
| 10 | [Pipe Maze](https://adventofcode.com/2023/day/10) | [Source](src/year2023/day10.rs) | 35 |
120120
| 11 | [Cosmic Expansion](https://adventofcode.com/2023/day/11) | [Source](src/year2023/day11.rs) | 12 |
121-
| 12 | [Hot Springs](https://adventofcode.com/2023/day/12) | [Source](src/year2023/day12.rs) | 387 |
121+
| 12 | [Hot Springs](https://adventofcode.com/2023/day/12) | [Source](src/year2023/day12.rs) | 455 |
122122
| 13 | [Point of Incidence](https://adventofcode.com/2023/day/13) | [Source](src/year2023/day13.rs) | 66 |
123123
| 14 | [Parabolic Reflector Dish](https://adventofcode.com/2023/day/14) | [Source](src/year2023/day14.rs) | 632 |
124124
| 15 | [Lens Library](https://adventofcode.com/2023/day/15) | [Source](src/year2023/day15.rs) | 84 |
@@ -188,7 +188,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
188188
| 15 | [Chiton](https://adventofcode.com/2021/day/15) | [Source](src/year2021/day15.rs) | 2403 |
189189
| 16 | [Packet Decoder](https://adventofcode.com/2021/day/16) | [Source](src/year2021/day16.rs) | 6 |
190190
| 17 | [Trick Shot](https://adventofcode.com/2021/day/17) | [Source](src/year2021/day17.rs) | 7 |
191-
| 18 | [Snailfish](https://adventofcode.com/2021/day/18) | [Source](src/year2021/day18.rs) | 404 |
191+
| 18 | [Snailfish](https://adventofcode.com/2021/day/18) | [Source](src/year2021/day18.rs) | 476 |
192192
| 19 | [Beacon Scanner](https://adventofcode.com/2021/day/19) | [Source](src/year2021/day19.rs) | 615 |
193193
| 20 | [Trench Map](https://adventofcode.com/2021/day/20) | [Source](src/year2021/day20.rs) | 2066 |
194194
| 21 | [Dirac Dice](https://adventofcode.com/2021/day/21) | [Source](src/year2021/day21.rs) | 278 |
@@ -277,7 +277,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
277277
| 8 | [Memory Maneuver](https://adventofcode.com/2018/day/8) | [Source](src/year2018/day08.rs) | 24 |
278278
| 9 | [Marble Mania](https://adventofcode.com/2018/day/9) | [Source](src/year2018/day09.rs) | 909 |
279279
| 10 | [The Stars Align](https://adventofcode.com/2018/day/10) | [Source](src/year2018/day10.rs) | 11 |
280-
| 11 | [Chronal Charge](https://adventofcode.com/2018/day/11) | [Source](src/year2018/day11.rs) | 1156 |
280+
| 11 | [Chronal Charge](https://adventofcode.com/2018/day/11) | [Source](src/year2018/day11.rs) | 1193 |
281281
| 12 | [Subterranean Sustainability](https://adventofcode.com/2018/day/12) | [Source](src/year2018/day12.rs) | 77 |
282282
| 13 | [Mine Cart Madness](https://adventofcode.com/2018/day/13) | [Source](src/year2018/day13.rs) | 349 |
283283
| 14 | [Chocolate Charts](https://adventofcode.com/2018/day/14) | [Source](src/year2018/day14.rs) | 24000 |
@@ -368,7 +368,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
368368
| 3 | [Perfectly Spherical Houses in a Vacuum](https://adventofcode.com/2015/day/3) | [Source](src/year2015/day03.rs) | 95 |
369369
| 4 | [The Ideal Stocking Stuffer](https://adventofcode.com/2015/day/4) | [Source](src/year2015/day04.rs) | 14000 |
370370
| 5 | [Doesn't He Have Intern-Elves For This?](https://adventofcode.com/2015/day/5) | [Source](src/year2015/day05.rs) | 38 |
371-
| 6 | [Probably a Fire Hazard](https://adventofcode.com/2015/day/6) | [Source](src/year2015/day06.rs) | 386 |
371+
| 6 | [Probably a Fire Hazard](https://adventofcode.com/2015/day/6) | [Source](src/year2015/day06.rs) | 454 |
372372
| 7 | [Some Assembly Required](https://adventofcode.com/2015/day/7) | [Source](src/year2015/day07.rs) | 27 |
373373
| 8 | [Matchsticks](https://adventofcode.com/2015/day/8) | [Source](src/year2015/day08.rs) | 5 |
374374
| 9 | [All in a Single Night](https://adventofcode.com/2015/day/9) | [Source](src/year2015/day09.rs) | 34 |

src/util/thread.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,26 @@ use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
66
use std::thread::*;
77

88
/// Usually the number of physical cores.
9-
fn threads() -> usize {
9+
pub fn threads() -> usize {
1010
available_parallelism().unwrap().get()
1111
}
1212

1313
/// Spawn `n` scoped threads, where `n` is the available parallelism.
14-
pub fn spawn<F>(f: F)
14+
pub fn spawn<F, R>(f: F) -> Vec<R>
1515
where
16-
F: Fn() + Copy + Send,
16+
F: Fn() -> R + Copy + Send,
17+
R: Send,
1718
{
1819
scope(|scope| {
20+
let mut handles = Vec::new();
21+
1922
for _ in 0..threads() {
20-
scope.spawn(f);
23+
let handle = scope.spawn(f);
24+
handles.push(handle);
2125
}
22-
});
26+
27+
handles.into_iter().flat_map(ScopedJoinHandle::join).collect()
28+
})
2329
}
2430

2531
/// Spawns `n` scoped threads that each receive a
@@ -28,9 +34,10 @@ where
2834
/// than other to process, used by popular libraries such as [rayon](https://github.com/rayon-rs/rayon).
2935
/// Processing at different rates also happens on many modern CPUs with
3036
/// [heterogeneous performance and efficiency cores](https://en.wikipedia.org/wiki/ARM_big.LITTLE).
31-
pub fn spawn_parallel_iterator<F, T>(items: &[T], f: F)
37+
pub fn spawn_parallel_iterator<F, R, T>(items: &[T], f: F) -> Vec<R>
3238
where
33-
F: Fn(ParIter<'_, T>) + Copy + Send,
39+
F: Fn(ParIter<'_, T>) -> R + Copy + Send,
40+
R: Send,
3441
T: Sync,
3542
{
3643
let threads = threads();
@@ -47,10 +54,15 @@ where
4754
let workers = workers.as_slice();
4855

4956
scope(|scope| {
57+
let mut handles = Vec::new();
58+
5059
for id in 0..threads {
51-
scope.spawn(move || f(ParIter { id, items, workers }));
60+
let handle = scope.spawn(move || f(ParIter { id, items, workers }));
61+
handles.push(handle);
5262
}
53-
});
63+
64+
handles.into_iter().flat_map(ScopedJoinHandle::join).collect()
65+
})
5466
}
5567

5668
pub struct ParIter<'a, T> {

src/year2015/day06.rs

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use crate::util::iter::*;
66
use crate::util::parse::*;
77
use crate::util::thread::*;
8-
use std::sync::atomic::{AtomicU32, Ordering};
98

109
#[derive(Clone, Copy)]
1110
enum Command {
@@ -62,24 +61,18 @@ pub fn parse(input: &str) -> Vec<Instruction> {
6261

6362
pub fn part1(input: &[Instruction]) -> u32 {
6463
let items: Vec<_> = (0..1000).collect();
65-
let atomic = AtomicU32::new(0);
66-
67-
spawn_parallel_iterator(&items, |iter| worker_one(input, &atomic, iter));
68-
atomic.into_inner()
64+
let result = spawn_parallel_iterator(&items, |iter| worker_one(input, iter));
65+
result.into_iter().sum()
6966
}
7067

7168
pub fn part2(input: &[Instruction]) -> u32 {
7269
let items: Vec<_> = (0..1000).collect();
73-
let atomic = AtomicU32::new(0);
74-
75-
spawn_parallel_iterator(&items, |iter| worker_two(input, &atomic, iter));
76-
atomic.into_inner()
70+
let result = spawn_parallel_iterator(&items, |iter| worker_two(input, iter));
71+
result.into_iter().sum()
7772
}
7873

79-
fn worker_one(input: &[Instruction], atomic: &AtomicU32, iter: ParIter<'_, usize>) {
80-
let mut result = 0;
81-
82-
for row in iter {
74+
fn worker_one(input: &[Instruction], iter: ParIter<'_, usize>) -> u32 {
75+
iter.map(|row| {
8376
let mut grid = [0_u8; 1_024];
8477

8578
for &Instruction { command, rectangle: Rectangle { x1, y1, x2, y2 } } in input {
@@ -93,16 +86,13 @@ fn worker_one(input: &[Instruction], atomic: &AtomicU32, iter: ParIter<'_, usize
9386
}
9487
}
9588

96-
result += grid.into_iter().map(|b| b as u32).sum::<u32>();
97-
}
98-
99-
atomic.fetch_add(result, Ordering::Relaxed);
89+
grid.into_iter().map(|b| b as u32).sum::<u32>()
90+
})
91+
.sum()
10092
}
10193

102-
fn worker_two(input: &[Instruction], atomic: &AtomicU32, iter: ParIter<'_, usize>) {
103-
let mut result = 0;
104-
105-
for row in iter {
94+
fn worker_two(input: &[Instruction], iter: ParIter<'_, usize>) -> u32 {
95+
iter.map(|row| {
10696
let mut grid = [0_u8; 1_024];
10797

10898
for &Instruction { command, rectangle: Rectangle { x1, y1, x2, y2 } } in input {
@@ -116,8 +106,7 @@ fn worker_two(input: &[Instruction], atomic: &AtomicU32, iter: ParIter<'_, usize
116106
}
117107
}
118108

119-
result += grid.into_iter().map(|b| b as u32).sum::<u32>();
120-
}
121-
122-
atomic.fetch_add(result, Ordering::Relaxed);
109+
grid.into_iter().map(|b| b as u32).sum::<u32>()
110+
})
111+
.sum()
123112
}

src/year2017/day15.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::util::hash::*;
1010
use crate::util::iter::*;
1111
use crate::util::math::*;
1212
use crate::util::parse::*;
13+
use crate::util::thread::*;
1314
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
1415
use std::sync::mpsc::{Receiver, Sender, channel};
1516
use std::thread;
@@ -44,7 +45,7 @@ pub fn parse(input: &str) -> Input {
4445

4546
thread::scope(|scope| {
4647
// Use all cores except one to generate blocks of numbers for judging.
47-
for _ in 0..thread::available_parallelism().unwrap().get() - 1 {
48+
for _ in 0..threads() - 1 {
4849
scope.spawn(|| sender(&shared, &tx));
4950
}
5051
// Judge batches serially.

src/year2018/day11.rs

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
//! so we can parallelize over multiple threads.
88
use crate::util::parse::*;
99
use crate::util::thread::*;
10-
use std::sync::Mutex;
1110

1211
pub struct Result {
1312
x: usize,
@@ -16,11 +15,6 @@ pub struct Result {
1615
power: i32,
1716
}
1817

19-
struct Shared {
20-
sat: Vec<i32>,
21-
mutex: Mutex<Vec<Result>>,
22-
}
23-
2418
pub fn parse(input: &str) -> Vec<Result> {
2519
let grid_serial_number: i32 = input.signed();
2620

@@ -45,9 +39,8 @@ pub fn parse(input: &str) -> Vec<Result> {
4539
// Use as many cores as possible to parallelize the search.
4640
// Smaller sizes take more time so use work stealing to keep all cores busy.
4741
let items: Vec<_> = (1..301).collect();
48-
let shared = Shared { sat, mutex: Mutex::new(Vec::new()) };
49-
spawn_parallel_iterator(&items, |iter| worker(&shared, iter));
50-
shared.mutex.into_inner().unwrap()
42+
let result = spawn_parallel_iterator(&items, |iter| worker(&sat, iter));
43+
result.into_iter().flatten().collect()
5144
}
5245

5346
pub fn part1(input: &[Result]) -> String {
@@ -60,15 +53,12 @@ pub fn part2(input: &[Result]) -> String {
6053
format!("{x},{y},{size}")
6154
}
6255

63-
fn worker(shared: &Shared, iter: ParIter<'_, usize>) {
64-
let result: Vec<_> = iter
65-
.map(|&size| {
66-
let (power, x, y) = square(&shared.sat, size);
67-
Result { x, y, size, power }
68-
})
69-
.collect();
70-
71-
shared.mutex.lock().unwrap().extend(result);
56+
fn worker(sat: &[i32], iter: ParIter<'_, usize>) -> Vec<Result> {
57+
iter.map(|&size| {
58+
let (power, x, y) = square(sat, size);
59+
Result { x, y, size, power }
60+
})
61+
.collect()
7262
}
7363

7464
/// Find the (x,y) coordinates and max power for a square of the specified size.

src/year2021/day18.rs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
//! always greater than or equal to zero, `-1` is used as a special sentinel value for non-leaf nodes.
3030
use crate::util::parse::*;
3131
use crate::util::thread::*;
32-
use std::sync::atomic::{AtomicI32, Ordering};
3332

3433
type Snailfish = [i32; 63];
3534

@@ -85,20 +84,13 @@ pub fn part2(input: &[Snailfish]) -> i32 {
8584
}
8685

8786
// Use as many cores as possible to parallelize the calculation.
88-
let shared = AtomicI32::new(0);
89-
spawn_parallel_iterator(&pairs, |iter| worker(&shared, iter));
90-
shared.into_inner()
87+
let result = spawn_parallel_iterator(&pairs, worker);
88+
result.into_iter().max().unwrap()
9189
}
9290

9391
/// Pair addition is independent so we can parallelize across multiple threads.
94-
fn worker(shared: &AtomicI32, iter: ParIter<'_, (&Snailfish, &Snailfish)>) {
95-
let mut partial = 0;
96-
97-
for (a, b) in iter {
98-
partial = partial.max(magnitude(&mut add(a, b)));
99-
}
100-
101-
shared.fetch_max(partial, Ordering::Relaxed);
92+
fn worker(iter: ParIter<'_, (&Snailfish, &Snailfish)>) -> i32 {
93+
iter.map(|&(a, b)| magnitude(&mut add(a, b))).max().unwrap()
10294
}
10395

10496
/// Add two snailfish numbers.

src/year2022/day11.rs

Lines changed: 16 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@
4343
//! [`iter_unsigned`]: ParseOps::iter_unsigned
4444
use crate::util::parse::*;
4545
use crate::util::thread::*;
46-
use std::sync::Mutex;
46+
47+
type Pair = (usize, u64);
4748

4849
pub struct Monkey {
4950
items: Vec<u64>,
@@ -59,19 +60,8 @@ pub enum Operation {
5960
Add(u64),
6061
}
6162

62-
type Pair = (usize, u64);
6363
type Business = [u64; 8];
6464

65-
struct Shared<'a> {
66-
monkeys: &'a [Monkey],
67-
mutex: Mutex<Exclusive>,
68-
}
69-
70-
struct Exclusive {
71-
pairs: Vec<Pair>,
72-
business: Business,
73-
}
74-
7565
/// Extract each Monkey's info from the flavor text. With the exception of the lines starting
7666
/// `Operation` we are only interested in the numbers on each line.
7767
pub fn parse(input: &str) -> Vec<Monkey> {
@@ -102,7 +92,7 @@ pub fn part2(input: &[Monkey]) -> u64 {
10292
}
10393

10494
/// Convenience wrapper to reuse common logic between part one and two.
105-
fn solve(monkeys: &[Monkey], play: impl Fn(&[Monkey], Vec<Pair>) -> Business) -> u64 {
95+
fn solve(monkeys: &[Monkey], play: impl Fn(&[Monkey], &[Pair]) -> Business) -> u64 {
10696
let mut pairs = Vec::new();
10797

10898
for (from, monkey) in monkeys.iter().enumerate() {
@@ -111,16 +101,16 @@ fn solve(monkeys: &[Monkey], play: impl Fn(&[Monkey], Vec<Pair>) -> Business) ->
111101
}
112102
}
113103

114-
let mut business = play(monkeys, pairs);
104+
let mut business = play(monkeys, &pairs);
115105
business.sort_unstable();
116106
business.iter().rev().take(2).product()
117107
}
118108

119109
/// Play 20 rounds dividing the worry level by 3 each inspection.
120-
fn sequential(monkeys: &[Monkey], pairs: Vec<Pair>) -> Business {
110+
fn sequential(monkeys: &[Monkey], pairs: &[Pair]) -> Business {
121111
let mut business = [0; 8];
122112

123-
for pair in pairs {
113+
for &pair in pairs {
124114
let extra = play(monkeys, 20, |x| x / 3, pair);
125115
business.iter_mut().enumerate().for_each(|(i, b)| *b += extra[i]);
126116
}
@@ -129,31 +119,21 @@ fn sequential(monkeys: &[Monkey], pairs: Vec<Pair>) -> Business {
129119
}
130120

131121
/// Play 10,000 rounds adjusting the worry level modulo the product of all the monkey's test values.
132-
fn parallel(monkeys: &[Monkey], pairs: Vec<Pair>) -> Business {
133-
let shared = Shared { monkeys, mutex: Mutex::new(Exclusive { pairs, business: [0; 8] }) };
134-
122+
fn parallel(monkeys: &[Monkey], pairs: &[Pair]) -> Business {
135123
// Use as many cores as possible to parallelize the calculation.
136-
spawn(|| worker(&shared));
124+
let result = spawn_parallel_iterator(pairs, |iter| worker(monkeys, iter));
137125

138-
shared.mutex.into_inner().unwrap().business
126+
let mut business = [0; 8];
127+
for extra in result.into_iter().flatten() {
128+
business.iter_mut().zip(extra).for_each(|(b, e)| *b += e);
129+
}
130+
business
139131
}
140132

141133
/// Multiple worker functions are executed in parallel, one per thread.
142-
fn worker(shared: &Shared<'_>) {
143-
let product: u64 = shared.monkeys.iter().map(|m| m.test).product();
144-
145-
loop {
146-
// Take an item from the queue until empty, using the mutex to allow access
147-
// to a single thread at a time.
148-
let Some(pair) = shared.mutex.lock().unwrap().pairs.pop() else {
149-
break;
150-
};
151-
152-
let extra = play(shared.monkeys, 10000, |x| x % product, pair);
153-
154-
let mut exclusive = shared.mutex.lock().unwrap();
155-
exclusive.business.iter_mut().enumerate().for_each(|(i, b)| *b += extra[i]);
156-
}
134+
fn worker(monkeys: &[Monkey], iter: ParIter<'_, Pair>) -> Vec<Business> {
135+
let product: u64 = monkeys.iter().map(|m| m.test).product();
136+
iter.map(|&pair| play(monkeys, 10000, |x| x % product, pair)).collect()
157137
}
158138

159139
/// Play an arbitrary number of rounds for a single item.

0 commit comments

Comments
 (0)