Skip to content

Commit 354e847

Browse files
committed
Faster approach using custom modulo function
1 parent 7c0973f commit 354e847

File tree

2 files changed

+45
-64
lines changed

2 files changed

+45
-64
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
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 |
315315
| 14 | [Disk Defragmentation](https://adventofcode.com/2017/day/14) | [Source](src/year2017/day14.rs) | 438 |
316-
| 15 | [Dueling Generators](https://adventofcode.com/2017/day/15) | [Source](src/year2017/day15.rs) | 26000 |
316+
| 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 |
319319
| 18 | [Duet](https://adventofcode.com/2017/day/18) | [Source](src/year2017/day18.rs) | 7 |

src/year2017/day15.rs

Lines changed: 44 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
1414
use std::sync::mpsc::{Receiver, Sender, channel};
1515
use std::thread;
1616

17+
const MOD: usize = 0x7fffffff;
1718
const PART_ONE: usize = 40_000_000;
1819
const PART_TWO: usize = 5_000_000;
1920
const BLOCK: usize = 50_000;
2021

21-
type Input = (u32, u32);
22+
type Input = (usize, usize);
2223

2324
/// State shared between all threads.
2425
pub struct Shared {
@@ -31,7 +32,7 @@ pub struct Shared {
3132
/// Generated numbers from `start` to `start + BLOCK`.
3233
struct Block {
3334
start: usize,
34-
ones: u32,
35+
ones: usize,
3536
fours: Vec<u16>,
3637
eights: Vec<u16>,
3738
}
@@ -51,30 +52,30 @@ pub fn parse(input: &str) -> Input {
5152
})
5253
}
5354

54-
pub fn part1(input: &Input) -> u32 {
55+
pub fn part1(input: &Input) -> usize {
5556
input.0
5657
}
5758

58-
pub fn part2(input: &Input) -> u32 {
59+
pub fn part2(input: &Input) -> usize {
5960
input.1
6061
}
6162

6263
fn sender(shared: &Shared, tx: &Sender<Block>) {
6364
while !shared.done.load(Ordering::Relaxed) {
6465
// Start at any point in the sequence using modular exponentiation.
6566
let start = shared.start.fetch_add(BLOCK, Ordering::Relaxed);
66-
let mut first = shared.first * 16807.mod_pow(start, 0x7fffffff);
67-
let mut second = shared.second * 48271.mod_pow(start, 0x7fffffff);
67+
let mut first = shared.first * 16807.mod_pow(start, MOD);
68+
let mut second = shared.second * 48271.mod_pow(start, MOD);
6869

69-
// Estimate capacity at one quarter or one eight, plus a little extra for variance.
70+
// Estimate capacity at one quarter or one eight.
7071
let mut ones = 0;
71-
let mut fours = Vec::with_capacity((BLOCK * 30) / 100);
72-
let mut eights = Vec::with_capacity((BLOCK * 15) / 100);
72+
let mut fours = Vec::with_capacity(BLOCK / 4);
73+
let mut eights = Vec::with_capacity(BLOCK / 8);
7374

7475
// Check part one pairs immediately while queueing part two pairs.
7576
for _ in 0..BLOCK {
76-
first = (first * 16807) % 0x7fffffff;
77-
second = (second * 48271) % 0x7fffffff;
77+
first = fast_mod(first * 16807);
78+
second = fast_mod(second * 48271);
7879

7980
let left = first as u16;
8081
let right = second as u16;
@@ -94,76 +95,56 @@ fn sender(shared: &Shared, tx: &Sender<Block>) {
9495
}
9596
}
9697

97-
fn receiver(shared: &Shared, rx: &Receiver<Block>) -> (u32, u32) {
98-
let mut remaining = PART_TWO;
99-
let mut part_two = 0;
100-
98+
fn receiver(shared: &Shared, rx: &Receiver<Block>) -> Input {
10199
let mut required = 0;
102100
let mut out_of_order = FastMap::new();
103-
let mut blocks = Vec::new();
104101

105-
let mut fours_block = 0;
106-
let mut fours_index = 0;
102+
let mut fours = Vec::with_capacity(PART_TWO + BLOCK);
103+
let mut eights = Vec::with_capacity(PART_TWO + BLOCK);
104+
let mut start = 0;
107105

108-
let mut eights_block = 0;
109-
let mut eights_index = 0;
106+
let mut part_one = 0;
107+
let mut part_two = 0;
110108

111-
while remaining > 0 {
109+
while required < PART_ONE || fours.len() < PART_TWO || eights.len() < PART_TWO {
112110
// Blocks could be received in any order, as there's no guarantee threads will finish
113111
// processing at the same time. The `start` field of the block defines the order they
114112
// must be added to the vec.
115-
while fours_block >= blocks.len() || eights_block >= blocks.len() {
116-
let block = rx.recv().unwrap();
117-
out_of_order.insert(block.start, block);
118-
119-
while let Some(next) = out_of_order.remove(&required) {
120-
blocks.push(next);
121-
required += BLOCK;
122-
}
123-
}
124-
125-
// Iterate over the minimum block size or numbers left to check.
126-
let fours = &blocks[fours_block].fours;
127-
let eights = &blocks[eights_block].eights;
128-
let iterations = remaining.min(fours.len() - fours_index).min(eights.len() - eights_index);
113+
let block = rx.recv().unwrap();
114+
out_of_order.insert(block.start, block);
129115

130-
remaining -= iterations;
116+
while let Some(block) = out_of_order.remove(&required) {
117+
required += BLOCK;
131118

132-
for _ in 0..iterations {
133-
if fours[fours_index] == eights[eights_index] {
134-
part_two += 1;
119+
if required <= PART_ONE {
120+
part_one += block.ones;
135121
}
136-
fours_index += 1;
137-
eights_index += 1;
138-
}
139122

140-
// If we've checked all the numbers in a block, advance to the next one.
141-
// This may require waiting for a worker thread to create it first.
142-
if fours_index == fours.len() {
143-
fours_block += 1;
144-
fours_index = 0;
145-
}
146-
if eights_index == eights.len() {
147-
eights_block += 1;
148-
eights_index = 0;
149-
}
150-
}
123+
if fours.len() < PART_TWO {
124+
fours.extend_from_slice(&block.fours);
125+
}
151126

152-
// Just in case, make sure we have enough blocks for part one.
153-
while required < PART_ONE {
154-
let block = rx.recv().unwrap();
155-
out_of_order.insert(block.start, block);
127+
if eights.len() < PART_TWO {
128+
eights.extend_from_slice(&block.eights);
129+
}
156130

157-
while let Some(next) = out_of_order.remove(&required) {
158-
blocks.push(next);
159-
required += BLOCK;
131+
let end = PART_TWO.min(fours.len()).min(eights.len());
132+
part_two +=
133+
fours[start..end].iter().zip(&eights[start..end]).filter(|(a, b)| a == b).count();
134+
start = end;
160135
}
161136
}
162137

163-
// Signal worker thread to finish.
138+
// Signal worker threads to finish.
164139
shared.done.store(true, Ordering::Relaxed);
165140

166-
// Return results.
167-
let part_one = blocks.iter().take(PART_ONE / BLOCK).map(|p| p.ones).sum();
168141
(part_one, part_two)
169142
}
143+
144+
#[inline]
145+
fn fast_mod(n: usize) -> usize {
146+
let low = n & MOD;
147+
let high = n >> 31;
148+
let sum = low + high;
149+
if sum < MOD { sum } else { sum - MOD }
150+
}

0 commit comments

Comments
 (0)