Skip to content

Commit

Permalink
Refactor IntCode to much faster single threaded design
Browse files Browse the repository at this point in the history
  • Loading branch information
maneatingape committed Sep 2, 2023
1 parent fc09196 commit e64251b
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 81 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,11 @@ pie
| 2 | [1202 Program Alarm](https://adventofcode.com/2019/day/2) | [Source](src/year2019/day02.rs) | 1 |
| 3 | [Crossed Wires](https://adventofcode.com/2019/day/3) | [Source](src/year2019/day03.rs) | 19 |
| 4 | [Secure Container](https://adventofcode.com/2019/day/4) | [Source](src/year2019/day04.rs) | 7 |
| 5 | [Sunny with a Chance of Asteroids](https://adventofcode.com/2019/day/5) | [Source](src/year2019/day05.rs) | 34 |
| 5 | [Sunny with a Chance of Asteroids](https://adventofcode.com/2019/day/5) | [Source](src/year2019/day05.rs) | 3 |
| 6 | [Universal Orbit Map](https://adventofcode.com/2019/day/6) | [Source](src/year2019/day06.rs) | 28 |
| 7 | [Amplification Circuit](https://adventofcode.com/2019/day/7) | [Source](src/year2019/day07.rs) | 30000 |
| 7 | [Amplification Circuit](https://adventofcode.com/2019/day/7) | [Source](src/year2019/day07.rs) | 275 |
| 8 | [Space Image Format](https://adventofcode.com/2019/day/8) | [Source](src/year2019/day08.rs) | 5 |
| 9 | [Sensor Boost](https://adventofcode.com/2019/day/9) | [Source](src/year2019/day09.rs) | 3088 |
| 9 | [Sensor Boost](https://adventofcode.com/2019/day/9) | [Source](src/year2019/day09.rs) | 1356 |

## 2015

Expand Down
8 changes: 4 additions & 4 deletions src/year2019/day05.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ pub fn part2(input: &[i64]) -> i64 {
/// Start `IntCode` computer in its own thread, sending a single initial value.
/// Receives multiple values from the output channel returning only the last one.
fn run(input: &[i64], value: i64) -> i64 {
let (tx, rx) = Computer::spawn(input);
let _ = tx.send(value);
let mut computer = Computer::new(input);
computer.input(&[value]);

let mut result = 0;
while let Ok(output) = rx.recv() {
result = output;
while let State::Output(next) = computer.run() {
result = next;
}
result
}
28 changes: 15 additions & 13 deletions src/year2019/day07.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ pub fn part1(input: &[i64]) -> i64 {

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

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

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

// Send each initial phase setting exactly once.
for (tx, &phase) in senders.iter().zip(slice.iter()) {
let _ = tx.send(phase);
for (computer, &phase) in computers.iter_mut().zip(slice.iter()) {
computer.input(&[phase]);
}

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

'outer: loop {
for (tx, rx) in senders.iter().zip(receivers.iter()) {
let _ = tx.send(signal);
let Ok(next) = rx.recv() else {
break 'outer;
};
signal = next;
for computer in &mut computers {
computer.input(&[signal]);
match computer.run() {
State::Output(next) => signal = next,
_ => break 'outer,
}
}
}

Expand Down
126 changes: 65 additions & 61 deletions src/year2019/day09.rs
Original file line number Diff line number Diff line change
@@ -1,119 +1,120 @@
//! # Sensor Boost
//!
//! This problem is essentially an unit test for the canonical full intcode computer
//! used heavily by other days.
//! This problem is essentially a unit test for the full intcode computer used by other days.
use crate::util::parse::*;
use intcode::*;

pub mod intcode {
use std::sync::mpsc::*;
use std::thread;
use std::collections::VecDeque;

pub enum State {
Input,
Output(i64),
Halted,
}

pub struct Computer {
pc: usize,
base: i64,
code: Vec<i64>,
input_rx: Receiver<i64>,
output_tx: Sender<i64>,
input: VecDeque<i64>,
}

impl Computer {
/// Spawns an `IntCode` computer in a new thread, returning an input and output channel
/// for communicating asynchronously with the computer via the opcodes 3 and 4.
pub fn spawn(code: &[i64]) -> (Sender<i64>, Receiver<i64>) {
let pc = 0;
let base = 0;
let code = code.to_vec();
let (input_tx, input_rx) = channel();
let (output_tx, output_rx) = channel();

let mut computer = Computer { pc, base, code, input_rx, output_tx };
thread::spawn(move || computer.run());
pub fn new(code: &[i64]) -> Computer {
Computer { pc: 0, base: 0, code: code.to_vec(), input: VecDeque::new() }
}

(input_tx, output_rx)
pub fn input(&mut self, slice: &[i64]) {
self.input.extend(slice);
}

/// Runs until a `99` opcode instruction is encountered.
fn run(&mut self) {
/// Runs until either the program needs input, outputs a value or encounters the halt
/// opcode. In the first two cases, the computer can be restarted by calling `run` again.
pub fn run(&mut self) -> State {
loop {
match self.code[self.pc] % 100 {
let code = self.code[self.pc];

match code % 100 {
// Add
1 => {
let value = self.read(1) + self.read(2);
self.write(3, value);
let first = self.address(code / 100, 1);
let second = self.address(code / 1000, 2);
let third = self.address(code / 10000, 3);
self.code[third] = self.code[first] + self.code[second];
self.pc += 4;
}
// Multiply
2 => {
let value = self.read(1) * self.read(2);
self.write(3, value);
let first = self.address(code / 100, 1);
let second = self.address(code / 1000, 2);
let third = self.address(code / 10000, 3);
self.code[third] = self.code[first] * self.code[second];
self.pc += 4;
}
// Read input channel
3 => {
let value = self.input_rx.recv().unwrap();
self.write(1, value);
let Some(value) = self.input.pop_front() else {
break State::Input;
};
let first = self.address(code / 100, 1);
self.code[first] = value;
self.pc += 2;
}
// Write output channel
4 => {
let value = self.read(1);
let _ = self.output_tx.send(value);
let first = self.address(code / 100, 1);
let value = self.code[first];
self.pc += 2;
break State::Output(value);
}
// Jump if true
5 => {
let first = self.read(1);
let second = self.read(2);
self.pc = if first == 0 { self.pc + 3 } else { second as usize };
let first = self.address(code / 100, 1);
let second = self.address(code / 1000, 2);
let value = self.code[first] == 0;
self.pc = if value { self.pc + 3 } else { self.code[second] as usize };
}
// Jump if false
6 => {
let first = self.read(1);
let second = self.read(2);
self.pc = if first == 0 { second as usize } else { self.pc + 3 };
let first = self.address(code / 100, 1);
let second = self.address(code / 1000, 2);
let value = self.code[first] == 0;
self.pc = if value { self.code[second] as usize } else { self.pc + 3 };
}
// Less than
7 => {
let value = self.read(1) < self.read(2);
self.write(3, value as i64);
let first = self.address(code / 100, 1);
let second = self.address(code / 1000, 2);
let third = self.address(code / 10000, 3);
let value = self.code[first] < self.code[second];
self.code[third] = value as i64;
self.pc += 4;
}
// Equals
8 => {
let value = self.read(1) == self.read(2);
self.write(3, value as i64);
let first = self.address(code / 100, 1);
let second = self.address(code / 1000, 2);
let third = self.address(code / 10000, 3);
let value = self.code[first] == self.code[second];
self.code[third] = value as i64;
self.pc += 4;
}
// Adjust relative base
9 => {
let value = self.read(1);
self.base += value;
let first = self.address(code / 100, 1);
self.base += self.code[first];
self.pc += 2;
}
_ => break,
_ => break State::Halted,
}
}
}

/// Convenience wrapper for reading a value
fn read(&mut self, offset: usize) -> i64 {
let index = self.address(offset);
self.code[index]
}

/// Convenience wrapper for writing a value
fn write(&mut self, offset: usize, value: i64) {
let index = self.address(offset);
self.code[index] = value;
}

/// Calculates an address using one of the three possible address modes.
/// If the address exceeds the size of the `code` vector then it is extended with 0 values.
fn address(&mut self, offset: usize) -> usize {
const FACTOR: [i64; 4] = [0, 100, 1000, 10000];
let mode = self.code[self.pc] / FACTOR[offset];

#[inline]
fn address(&mut self, mode: i64, offset: usize) -> usize {
let index = match mode % 10 {
0 => self.code[self.pc + offset] as usize,
1 => self.pc + offset,
Expand Down Expand Up @@ -143,7 +144,10 @@ pub fn part2(input: &[i64]) -> i64 {
}

fn run(input: &[i64], value: i64) -> i64 {
let (tx, rx) = Computer::spawn(input);
let _ = tx.send(value);
rx.recv().unwrap()
let mut computer = Computer::new(input);
computer.input(&[value]);
match computer.run() {
State::Output(result) => result,
_ => unreachable!(),
}
}

0 comments on commit e64251b

Please sign in to comment.