Skip to content

Commit 96b0422

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

File tree

12 files changed

+118
-202
lines changed

12 files changed

+118
-202
lines changed

README.md

Lines changed: 8 additions & 8 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 |
@@ -312,7 +312,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
312312
| 11 | [Hex Ed](https://adventofcode.com/2017/day/11) | [Source](src/year2017/day11.rs) | 15 |
313313
| 12 | [Digital Plumber](https://adventofcode.com/2017/day/12) | [Source](src/year2017/day12.rs) | 61 |
314314
| 13 | [Packet Scanners](https://adventofcode.com/2017/day/13) | [Source](src/year2017/day13.rs) | 1 |
315-
| 14 | [Disk Defragmentation](https://adventofcode.com/2017/day/14) | [Source](src/year2017/day14.rs) | 438 |
315+
| 14 | [Disk Defragmentation](https://adventofcode.com/2017/day/14) | [Source](src/year2017/day14.rs) | 488 |
316316
| 15 | [Dueling Generators](https://adventofcode.com/2017/day/15) | [Source](src/year2017/day15.rs) | 20000 |
317317
| 16 | [Permutation Promenade](https://adventofcode.com/2017/day/16) | [Source](src/year2017/day16.rs) | 68 |
318318
| 17 | [Spinlock](https://adventofcode.com/2017/day/17) | [Source](src/year2017/day17.rs) | 85 |
@@ -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/day14.rs

Lines changed: 10 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,16 @@
66
//! [`Day 10`]: crate::year2017::day10
77
//! [`Day 12`]: crate::year2017::day12
88
use crate::util::thread::*;
9-
use std::sync::Mutex;
10-
use std::sync::atomic::{AtomicUsize, Ordering};
11-
12-
/// Atomics can be safely shared between threads.
13-
pub struct Shared {
14-
prefix: String,
15-
counter: AtomicUsize,
16-
mutex: Mutex<Exclusive>,
17-
}
18-
19-
/// Regular data structures need to be protected by a mutex.
20-
struct Exclusive {
21-
grid: Vec<u8>,
22-
}
239

2410
/// Parallelize the hashing as each row is independent.
2511
pub fn parse(input: &str) -> Vec<u8> {
26-
let shared = Shared {
27-
prefix: input.trim().to_owned(),
28-
counter: AtomicUsize::new(0),
29-
mutex: Mutex::new(Exclusive { grid: vec![0; 0x4000] }),
30-
};
31-
32-
// Use as many cores as possible to parallelize the hashing.
33-
spawn(|| worker(&shared));
12+
let prefix = &input.trim();
13+
let rows: Vec<_> = (0..128).collect();
14+
let result = spawn_parallel_iterator(&rows, |iter| worker(prefix, iter));
3415

35-
shared.mutex.into_inner().unwrap().grid
16+
let mut sorted: Vec<_> = result.into_iter().flatten().collect();
17+
sorted.sort_unstable_by_key(|&(index, _)| index);
18+
sorted.into_iter().flat_map(|(_, row)| row).collect()
3619
}
3720

3821
pub fn part1(input: &[u8]) -> u32 {
@@ -56,30 +39,18 @@ pub fn part2(input: &[u8]) -> u32 {
5639

5740
/// Each worker thread chooses the next available index then computes the hash and patches the
5841
/// final vec with the result.
59-
fn worker(shared: &Shared) {
60-
loop {
61-
let index = shared.counter.fetch_add(1, Ordering::Relaxed);
62-
if index >= 128 {
63-
break;
64-
}
65-
66-
let row = fill_row(&shared.prefix, index);
67-
let start = index * 128;
68-
let end = start + 128;
69-
70-
let mut exclusive = shared.mutex.lock().unwrap();
71-
exclusive.grid[start..end].copy_from_slice(&row);
72-
}
42+
fn worker(prefix: &str, iter: ParIter<'_, usize>) -> Vec<(usize, Vec<u8>)> {
43+
iter.map(|&index| (index, fill_row(prefix, index))).collect()
7344
}
7445

7546
/// Compute the knot hash for a row and expand into an array of bytes.
76-
fn fill_row(prefix: &str, index: usize) -> [u8; 128] {
47+
fn fill_row(prefix: &str, index: usize) -> Vec<u8> {
7748
let s = format!("{prefix}-{index}");
7849
let mut lengths: Vec<_> = s.bytes().map(|b| b as usize).collect();
7950
lengths.extend([17, 31, 73, 47, 23]);
8051

8152
let knot = knot_hash(&lengths);
82-
let mut result = [0; 128];
53+
let mut result = vec![0; 128];
8354

8455
for (i, chunk) in knot.chunks_exact(16).enumerate() {
8556
let reduced = chunk.iter().fold(0, |acc, n| acc ^ n);

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.

0 commit comments

Comments
 (0)