Skip to content

Commit e64251b

Browse files
committed
Refactor IntCode to much faster single threaded design
1 parent fc09196 commit e64251b

File tree

4 files changed

+87
-81
lines changed

4 files changed

+87
-81
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,11 @@ pie
197197
| 2 | [1202 Program Alarm](https://adventofcode.com/2019/day/2) | [Source](src/year2019/day02.rs) | 1 |
198198
| 3 | [Crossed Wires](https://adventofcode.com/2019/day/3) | [Source](src/year2019/day03.rs) | 19 |
199199
| 4 | [Secure Container](https://adventofcode.com/2019/day/4) | [Source](src/year2019/day04.rs) | 7 |
200-
| 5 | [Sunny with a Chance of Asteroids](https://adventofcode.com/2019/day/5) | [Source](src/year2019/day05.rs) | 34 |
200+
| 5 | [Sunny with a Chance of Asteroids](https://adventofcode.com/2019/day/5) | [Source](src/year2019/day05.rs) | 3 |
201201
| 6 | [Universal Orbit Map](https://adventofcode.com/2019/day/6) | [Source](src/year2019/day06.rs) | 28 |
202-
| 7 | [Amplification Circuit](https://adventofcode.com/2019/day/7) | [Source](src/year2019/day07.rs) | 30000 |
202+
| 7 | [Amplification Circuit](https://adventofcode.com/2019/day/7) | [Source](src/year2019/day07.rs) | 275 |
203203
| 8 | [Space Image Format](https://adventofcode.com/2019/day/8) | [Source](src/year2019/day08.rs) | 5 |
204-
| 9 | [Sensor Boost](https://adventofcode.com/2019/day/9) | [Source](src/year2019/day09.rs) | 3088 |
204+
| 9 | [Sensor Boost](https://adventofcode.com/2019/day/9) | [Source](src/year2019/day09.rs) | 1356 |
205205

206206
## 2015
207207

src/year2019/day05.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ pub fn part2(input: &[i64]) -> i64 {
1717
/// Start `IntCode` computer in its own thread, sending a single initial value.
1818
/// Receives multiple values from the output channel returning only the last one.
1919
fn run(input: &[i64], value: i64) -> i64 {
20-
let (tx, rx) = Computer::spawn(input);
21-
let _ = tx.send(value);
20+
let mut computer = Computer::new(input);
21+
computer.input(&[value]);
2222

2323
let mut result = 0;
24-
while let Ok(output) = rx.recv() {
25-
result = output;
24+
while let State::Output(next) = computer.run() {
25+
result = next;
2626
}
2727
result
2828
}

src/year2019/day07.rs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ pub fn part1(input: &[i64]) -> i64 {
2626

2727
// Send exactly 2 inputs and receive exactly 1 output per amplifier.
2828
for &phase in slice {
29-
let (tx, rx) = Computer::spawn(input);
30-
let _ = tx.send(phase);
31-
let _ = tx.send(signal);
32-
signal = rx.recv().unwrap();
29+
let mut computer = Computer::new(input);
30+
computer.input(&[phase, signal]);
31+
match computer.run() {
32+
State::Output(next) => signal = next,
33+
_ => unreachable!(),
34+
}
3335
}
3436

3537
result = result.max(signal);
@@ -43,23 +45,23 @@ pub fn part2(input: &[i64]) -> i64 {
4345
let mut result = 0;
4446

4547
let feedback = |slice: &[i64]| {
46-
let (senders, receivers): (Vec<_>, Vec<_>) = (0..5).map(|_| Computer::spawn(input)).unzip();
48+
let mut computers: Vec<_> = (0..5).map(|_| Computer::new(input)).collect();
4749

4850
// Send each initial phase setting exactly once.
49-
for (tx, &phase) in senders.iter().zip(slice.iter()) {
50-
let _ = tx.send(phase);
51+
for (computer, &phase) in computers.iter_mut().zip(slice.iter()) {
52+
computer.input(&[phase]);
5153
}
5254

5355
// Chain amplifier inputs and ouputs in a loop until all threads finish.
5456
let mut signal = 0;
5557

5658
'outer: loop {
57-
for (tx, rx) in senders.iter().zip(receivers.iter()) {
58-
let _ = tx.send(signal);
59-
let Ok(next) = rx.recv() else {
60-
break 'outer;
61-
};
62-
signal = next;
59+
for computer in &mut computers {
60+
computer.input(&[signal]);
61+
match computer.run() {
62+
State::Output(next) => signal = next,
63+
_ => break 'outer,
64+
}
6365
}
6466
}
6567

src/year2019/day09.rs

Lines changed: 65 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,120 @@
11
//! # Sensor Boost
22
//!
3-
//! This problem is essentially an unit test for the canonical full intcode computer
4-
//! used heavily by other days.
3+
//! This problem is essentially a unit test for the full intcode computer used by other days.
54
use crate::util::parse::*;
65
use intcode::*;
76

87
pub mod intcode {
9-
use std::sync::mpsc::*;
10-
use std::thread;
8+
use std::collections::VecDeque;
9+
10+
pub enum State {
11+
Input,
12+
Output(i64),
13+
Halted,
14+
}
1115

1216
pub struct Computer {
1317
pc: usize,
1418
base: i64,
1519
code: Vec<i64>,
16-
input_rx: Receiver<i64>,
17-
output_tx: Sender<i64>,
20+
input: VecDeque<i64>,
1821
}
1922

2023
impl Computer {
21-
/// Spawns an `IntCode` computer in a new thread, returning an input and output channel
22-
/// for communicating asynchronously with the computer via the opcodes 3 and 4.
23-
pub fn spawn(code: &[i64]) -> (Sender<i64>, Receiver<i64>) {
24-
let pc = 0;
25-
let base = 0;
26-
let code = code.to_vec();
27-
let (input_tx, input_rx) = channel();
28-
let (output_tx, output_rx) = channel();
29-
30-
let mut computer = Computer { pc, base, code, input_rx, output_tx };
31-
thread::spawn(move || computer.run());
24+
pub fn new(code: &[i64]) -> Computer {
25+
Computer { pc: 0, base: 0, code: code.to_vec(), input: VecDeque::new() }
26+
}
3227

33-
(input_tx, output_rx)
28+
pub fn input(&mut self, slice: &[i64]) {
29+
self.input.extend(slice);
3430
}
3531

36-
/// Runs until a `99` opcode instruction is encountered.
37-
fn run(&mut self) {
32+
/// Runs until either the program needs input, outputs a value or encounters the halt
33+
/// opcode. In the first two cases, the computer can be restarted by calling `run` again.
34+
pub fn run(&mut self) -> State {
3835
loop {
39-
match self.code[self.pc] % 100 {
36+
let code = self.code[self.pc];
37+
38+
match code % 100 {
4039
// Add
4140
1 => {
42-
let value = self.read(1) + self.read(2);
43-
self.write(3, value);
41+
let first = self.address(code / 100, 1);
42+
let second = self.address(code / 1000, 2);
43+
let third = self.address(code / 10000, 3);
44+
self.code[third] = self.code[first] + self.code[second];
4445
self.pc += 4;
4546
}
4647
// Multiply
4748
2 => {
48-
let value = self.read(1) * self.read(2);
49-
self.write(3, value);
49+
let first = self.address(code / 100, 1);
50+
let second = self.address(code / 1000, 2);
51+
let third = self.address(code / 10000, 3);
52+
self.code[third] = self.code[first] * self.code[second];
5053
self.pc += 4;
5154
}
5255
// Read input channel
5356
3 => {
54-
let value = self.input_rx.recv().unwrap();
55-
self.write(1, value);
57+
let Some(value) = self.input.pop_front() else {
58+
break State::Input;
59+
};
60+
let first = self.address(code / 100, 1);
61+
self.code[first] = value;
5662
self.pc += 2;
5763
}
5864
// Write output channel
5965
4 => {
60-
let value = self.read(1);
61-
let _ = self.output_tx.send(value);
66+
let first = self.address(code / 100, 1);
67+
let value = self.code[first];
6268
self.pc += 2;
69+
break State::Output(value);
6370
}
6471
// Jump if true
6572
5 => {
66-
let first = self.read(1);
67-
let second = self.read(2);
68-
self.pc = if first == 0 { self.pc + 3 } else { second as usize };
73+
let first = self.address(code / 100, 1);
74+
let second = self.address(code / 1000, 2);
75+
let value = self.code[first] == 0;
76+
self.pc = if value { self.pc + 3 } else { self.code[second] as usize };
6977
}
7078
// Jump if false
7179
6 => {
72-
let first = self.read(1);
73-
let second = self.read(2);
74-
self.pc = if first == 0 { second as usize } else { self.pc + 3 };
80+
let first = self.address(code / 100, 1);
81+
let second = self.address(code / 1000, 2);
82+
let value = self.code[first] == 0;
83+
self.pc = if value { self.code[second] as usize } else { self.pc + 3 };
7584
}
7685
// Less than
7786
7 => {
78-
let value = self.read(1) < self.read(2);
79-
self.write(3, value as i64);
87+
let first = self.address(code / 100, 1);
88+
let second = self.address(code / 1000, 2);
89+
let third = self.address(code / 10000, 3);
90+
let value = self.code[first] < self.code[second];
91+
self.code[third] = value as i64;
8092
self.pc += 4;
8193
}
8294
// Equals
8395
8 => {
84-
let value = self.read(1) == self.read(2);
85-
self.write(3, value as i64);
96+
let first = self.address(code / 100, 1);
97+
let second = self.address(code / 1000, 2);
98+
let third = self.address(code / 10000, 3);
99+
let value = self.code[first] == self.code[second];
100+
self.code[third] = value as i64;
86101
self.pc += 4;
87102
}
88103
// Adjust relative base
89104
9 => {
90-
let value = self.read(1);
91-
self.base += value;
105+
let first = self.address(code / 100, 1);
106+
self.base += self.code[first];
92107
self.pc += 2;
93108
}
94-
_ => break,
109+
_ => break State::Halted,
95110
}
96111
}
97112
}
98113

99-
/// Convenience wrapper for reading a value
100-
fn read(&mut self, offset: usize) -> i64 {
101-
let index = self.address(offset);
102-
self.code[index]
103-
}
104-
105-
/// Convenience wrapper for writing a value
106-
fn write(&mut self, offset: usize, value: i64) {
107-
let index = self.address(offset);
108-
self.code[index] = value;
109-
}
110-
111114
/// Calculates an address using one of the three possible address modes.
112115
/// If the address exceeds the size of the `code` vector then it is extended with 0 values.
113-
fn address(&mut self, offset: usize) -> usize {
114-
const FACTOR: [i64; 4] = [0, 100, 1000, 10000];
115-
let mode = self.code[self.pc] / FACTOR[offset];
116-
116+
#[inline]
117+
fn address(&mut self, mode: i64, offset: usize) -> usize {
117118
let index = match mode % 10 {
118119
0 => self.code[self.pc + offset] as usize,
119120
1 => self.pc + offset,
@@ -143,7 +144,10 @@ pub fn part2(input: &[i64]) -> i64 {
143144
}
144145

145146
fn run(input: &[i64], value: i64) -> i64 {
146-
let (tx, rx) = Computer::spawn(input);
147-
let _ = tx.send(value);
148-
rx.recv().unwrap()
147+
let mut computer = Computer::new(input);
148+
computer.input(&[value]);
149+
match computer.run() {
150+
State::Output(result) => result,
151+
_ => unreachable!(),
152+
}
149153
}

0 commit comments

Comments
 (0)