Skip to content

Commit 222aa47

Browse files
committed
Simpler and slightly faster threading
1 parent 29f1a5d commit 222aa47

File tree

2 files changed

+57
-57
lines changed

2 files changed

+57
-57
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
347347
| 14 | [One-Time Pad](https://adventofcode.com/2016/day/14) | [Source](src/year2016/day14.rs) | 72000 |
348348
| 15 | [Timing is Everything](https://adventofcode.com/2016/day/15) | [Source](src/year2016/day15.rs) | 1 |
349349
| 16 | [Dragon Checksum](https://adventofcode.com/2016/day/16) | [Source](src/year2016/day16.rs) | 1 |
350-
| 17 | [Two Steps Forward](https://adventofcode.com/2016/day/17) | [Source](src/year2016/day17.rs) | 3858 |
350+
| 17 | [Two Steps Forward](https://adventofcode.com/2016/day/17) | [Source](src/year2016/day17.rs) | 3606 |
351351
| 18 | [Like a Rogue](https://adventofcode.com/2016/day/18) | [Source](src/year2016/day18.rs) | 400 |
352352
| 19 | [An Elephant Named Joseph](https://adventofcode.com/2016/day/19) | [Source](src/year2016/day19.rs) | 1 |
353353
| 20 | [Firewall Rules](https://adventofcode.com/2016/day/20) | [Source](src/year2016/day20.rs) | 21 |

src/year2016/day17.rs

Lines changed: 56 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
//! Keeping each thread busy and spreading the work as evenly as possible is quite tricky. Some
55
//! paths can dead-end quickly while others can take the majority of exploration time.
66
//!
7-
//! To solve this we implement a very simple version of
8-
//! [work stealing](https://en.wikipedia.org/wiki/Work_stealing). Threads process paths locally,
7+
//! To solve this we implement a very simple version of work sharing. Threads process paths locally
98
//! stopping every now and then to return paths to a global queue. This allows other threads that
109
//! have run out of work to pickup new paths to process.
1110
//!
@@ -16,23 +15,19 @@ use crate::util::md5::*;
1615
use crate::util::thread::*;
1716
use std::sync::{Condvar, Mutex};
1817

19-
type Input = (String, usize);
18+
type Input = (Vec<u8>, usize);
2019
type Item = (u8, u8, usize, Vec<u8>);
2120

2221
struct State {
2322
todo: Vec<Item>,
24-
min: String,
23+
min: Vec<u8>,
2524
max: usize,
26-
}
27-
28-
struct Exclusive {
29-
global: State,
3025
inflight: usize,
3126
}
3227

3328
struct Shared {
3429
prefix: usize,
35-
mutex: Mutex<Exclusive>,
30+
mutex: Mutex<State>,
3631
not_empty: Condvar,
3732
}
3833

@@ -43,19 +38,18 @@ pub fn parse(input: &str) -> Input {
4338
let start = (0, 0, prefix, extend(input, prefix, 0));
4439

4540
// State shared between threads.
46-
let global = State { todo: vec![start], min: String::new(), max: 0 };
47-
let exclusive = Exclusive { global, inflight: 0 };
48-
let shared = Shared { prefix, mutex: Mutex::new(exclusive), not_empty: Condvar::new() };
41+
let state = State { todo: vec![start], min: vec![], max: 0, inflight: threads() };
42+
let shared = Shared { prefix, mutex: Mutex::new(state), not_empty: Condvar::new() };
4943

5044
// Search paths in parallel.
5145
spawn(|| worker(&shared));
5246

53-
let global = shared.mutex.into_inner().unwrap().global;
54-
(global.min, global.max)
47+
let state = shared.mutex.into_inner().unwrap();
48+
(state.min, state.max)
5549
}
5650

5751
pub fn part1(input: &Input) -> &str {
58-
&input.0
52+
str::from_utf8(&input.0).unwrap()
5953
}
6054

6155
pub fn part2(input: &Input) -> usize {
@@ -65,45 +59,47 @@ pub fn part2(input: &Input) -> usize {
6559
/// Process local work items, stopping every now and then to redistribute items back to global pool.
6660
/// This prevents threads idling or hotspotting.
6761
fn worker(shared: &Shared) {
68-
let mut local = State { todo: Vec::new(), min: String::new(), max: 0 };
62+
let mut local = State { todo: vec![], min: vec![], max: 0, inflight: 0 };
6963

7064
loop {
71-
let mut exclusive = shared.mutex.lock().unwrap();
72-
let item = loop {
73-
// Pickup available work.
74-
if let Some(item) = exclusive.global.todo.pop() {
75-
exclusive.inflight += 1;
76-
break item;
77-
}
78-
// If no work available and no other thread is doing anything, then we're done.
79-
if exclusive.inflight == 0 {
80-
return;
81-
}
82-
// Put thread to sleep until another thread notifies us that work is available.
83-
// This avoids busy looping on the mutex.
84-
exclusive = shared.not_empty.wait(exclusive).unwrap();
85-
};
86-
87-
// Drop mutex to release lock and allow other threads access.
88-
drop(exclusive);
89-
9065
// Process local work items.
91-
local.todo.push(item);
9266
explore(shared, &mut local);
9367

94-
// Redistribute local work items back to the global queue. Update min and max paths.
95-
let mut exclusive = shared.mutex.lock().unwrap();
96-
let global = &mut exclusive.global;
68+
// Acquire mutex.
69+
let mut state = shared.mutex.lock().unwrap();
9770

98-
global.todo.append(&mut local.todo);
99-
if global.min.is_empty() || local.min.len() < global.min.len() {
100-
global.min = local.min.clone();
71+
// Update min and max paths.
72+
if state.min.is_empty() || local.min.len() < state.min.len() {
73+
state.min.clone_from(&local.min);
74+
}
75+
state.max = state.max.max(local.max);
76+
77+
if local.todo.is_empty() {
78+
// Mark ourselves as idle then notify all other threads in case we're done.
79+
state.inflight -= 1;
80+
shared.not_empty.notify_all();
81+
82+
loop {
83+
// Pickup available work.
84+
if let Some(item) = state.todo.pop() {
85+
state.inflight += 1;
86+
local.todo.push(item);
87+
break;
88+
}
89+
// If no work available and no other thread is doing anything, then we're done.
90+
if state.inflight == 0 {
91+
return;
92+
}
93+
// Put thread to sleep until another thread notifies us that work is available.
94+
// This avoids busy looping on the mutex.
95+
state = shared.not_empty.wait(state).unwrap();
96+
}
97+
} else {
98+
// Redistribute excess local work items back to the global queue then notify all other
99+
// threads that there is new work available.
100+
state.todo.extend(local.todo.drain(1..));
101+
shared.not_empty.notify_all();
101102
}
102-
global.max = global.max.max(local.max);
103-
104-
// Mark ourselves as idle then notify all other threads that there is new work available.
105-
exclusive.inflight -= 1;
106-
shared.not_empty.notify_all();
107103
}
108104
}
109105

@@ -121,37 +117,41 @@ fn explore(shared: &Shared, local: &mut State) {
121117
let adjusted = size - shared.prefix;
122118
if local.min.is_empty() || adjusted < local.min.len() {
123119
// Remove salt and padding.
124-
let middle = path[shared.prefix..size].to_vec();
125-
local.min = String::from_utf8(middle).unwrap();
120+
local.min = path[shared.prefix..size].to_vec();
126121
}
127122
local.max = local.max.max(adjusted);
128123
} else {
129124
// Explore other paths.
130125
let [result, ..] = hash(&mut path, size);
131126

132-
if y > 0 && ((result >> 28) & 0xf) > 0xa {
127+
if y > 0 && is_open(result, 28) {
133128
local.todo.push((x, y - 1, size + 1, extend(&path, size, b'U')));
134129
}
135-
if y < 3 && ((result >> 24) & 0xf) > 0xa {
130+
if y < 3 && is_open(result, 24) {
136131
local.todo.push((x, y + 1, size + 1, extend(&path, size, b'D')));
137132
}
138-
if x > 0 && ((result >> 20) & 0xf) > 0xa {
133+
if x > 0 && is_open(result, 20) {
139134
local.todo.push((x - 1, y, size + 1, extend(&path, size, b'L')));
140135
}
141-
if x < 3 && ((result >> 16) & 0xf) > 0xa {
136+
if x < 3 && is_open(result, 16) {
142137
local.todo.push((x + 1, y, size + 1, extend(&path, size, b'R')));
143138
}
144139
}
145140
}
146141
}
147142

143+
/// Check if a door is open based on MD5 hex digit (b-f means open).
144+
#[inline]
145+
fn is_open(hash: u32, shift: u32) -> bool {
146+
((hash >> shift) & 0xf) > 0xa
147+
}
148+
148149
/// Convenience function to generate new path.
149150
fn extend(src: &[u8], size: usize, b: u8) -> Vec<u8> {
150151
// Leave room for MD5 padding.
151-
let padded = buffer_size(size + 1);
152-
let mut next = vec![0; padded];
152+
let mut next = vec![0; buffer_size(size + 1)];
153153
// Copy existing path and next step.
154-
next[0..size].copy_from_slice(&src[0..size]);
154+
next[..size].copy_from_slice(&src[..size]);
155155
next[size] = b;
156156
next
157157
}

0 commit comments

Comments
 (0)