Skip to content

Commit 10a4a40

Browse files
committed
Creates an optimized intcode computer
- Reuses objects / arrays for modes, values, and parsed ops rather than recreating them every time. - Adds base-cases for op function calls, rather than always spreading our args.
2 parents cc30e1f + c70ad7d commit 10a4a40

File tree

3 files changed

+80
-35
lines changed

3 files changed

+80
-35
lines changed

2019/19/intcode-computer.js renamed to 2019/19/intcode-computer-optimized.js

Lines changed: 73 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ class Computer {
3535
this.replenish_input = replenish_input;
3636
this.outputs = [];
3737

38+
this.parseOpTime = 0;
39+
3840
this.OPS = {
3941
[ADD]: {
4042
name: ADD,
@@ -60,7 +62,7 @@ class Computer {
6062
name: INP,
6163
realName: 'INP',
6264
params: 1,
63-
fn: a => {
65+
fn: (a) => {
6466
this.memory[a] = this.inputs.shift();
6567
if (this.replenish_input !== undefined) {
6668
this.inputs.push(this.replenish_input);
@@ -73,14 +75,14 @@ class Computer {
7375
name: OUT,
7476
realName: 'OUT',
7577
params: 1,
76-
fn: a => this.output(a),
78+
fn: (a) => this.output(a),
7779
},
7880

7981
[ARB]: {
8082
name: ARB,
8183
realName: 'ARB',
8284
params: 1,
83-
fn: a => (this.relative_base += a),
85+
fn: (a) => (this.relative_base += a),
8486
},
8587

8688
[STP]: {
@@ -139,6 +141,32 @@ class Computer {
139141
},
140142
};
141143

144+
const ops_list = Object.values(this.OPS);
145+
const max_params = Math.max(...ops_list.map((v) => v.params));
146+
const shared_modes = Array(max_params).fill('0');
147+
148+
/**
149+
* Use shared arrays for `modes` and `value`.
150+
*
151+
* We can share a single array for `modes` since we know
152+
* the number of params for an op, meaning if an op has
153+
* 2 params, we can determine the modes for the 1st and
154+
* 2nd values, and the 3rd value will just be "junk" data
155+
* we'll ignore.
156+
*
157+
* The `values` for the ops need to be unique though, since
158+
* we spread out the values into our function call. Also
159+
* good to note that another optimization is we have hard-coded
160+
* fn calls for the number of params. `fn(...[1, 2])` is
161+
* slower than `fn(1, 2)`, so we code for 0 - 3 params, with
162+
* a default case of a spread. Similar libraries like lodash
163+
* do this.
164+
*/
165+
for (let op of ops_list) {
166+
op.modes = shared_modes;
167+
op.values = Array(op.params).fill(0);
168+
}
169+
142170
this.halted = false;
143171
}
144172

@@ -170,23 +198,25 @@ class Computer {
170198

171199
let full_op = temp_op.padStart(op.params + 2, '0');
172200

173-
let modes = [];
174-
175201
// "Parameter modes are single digits, one per parameter, read **right-to-left** from the opcode"
176202
for (let i = op.params - 1; i >= 0; i--) {
177-
modes.push(full_op[i]);
203+
// [0,1,2,3,4,5]
204+
// ^ ops.params = 6
205+
// 5 -> 0 # |(5 - 6 + 1)| = | 0| = 0
206+
// 4 -> 1 # |(4 - 6 + 1)| = |-1| = 1
207+
// 3 -> 2 # |(3 - 6 + 1)| = |-2| = 2
208+
// 2 -> 3 # |(2 - 6 + 1)| = |-3| = 3
209+
// 1 -> 4 # |(1 - 6 + 1)| = |-4| = 4
210+
// 0 -> 5 # |(0 - 6 + 1)| = |-5| = 5
211+
op.modes[Math.abs(i - op.params + 1)] = full_op[i];
178212
}
179213

180-
return {
181-
...op,
182-
modes,
183-
};
214+
return op;
184215
}
185216

186-
runOp({ modes, fn, jumps, write }) {
217+
runOp({ modes, values, params, fn, jumps, write }) {
187218
this.pointer++;
188-
let values = [];
189-
for (let i = 0; i < modes.length; i++) {
219+
for (let i = 0; i < params; i++) {
190220
let mode = modes[i];
191221
let value = this.memory[this.pointer + i];
192222

@@ -256,7 +286,7 @@ class Computer {
256286
* - I am running an op that does _not_ write to memory
257287
* - Or if I am not at the last parameter in the op
258288
*/
259-
const can_switch_to_position = !write || i < modes.length - 1;
289+
const can_switch_to_position = !write || i < params - 1;
260290

261291
if (can_switch_to_position && mode === POSITION_MODE) {
262292
value = this.memory[value];
@@ -280,14 +310,40 @@ class Computer {
280310
value = 0;
281311
}
282312

283-
values.push(value);
313+
values[i] = value;
284314
}
285315

286316
// If result is `true`, we moved the pointer
287-
let result = fn(...values);
317+
let result;
318+
319+
/**
320+
* Always spreading args is slow, so we can create a few base cases
321+
* (actually all our base cases) to speed up these function calls.
322+
* We still have a default case if for some reason we have an op
323+
* with more than 3 params (we don't), so this code is future proof,
324+
* but this change offers a ~2x speed improvement.
325+
*/
326+
switch (params) {
327+
case 0:
328+
result = fn();
329+
break;
330+
case 1:
331+
result = fn(values[0]);
332+
break;
333+
case 2:
334+
result = fn(values[0], values[1]);
335+
break;
336+
case 3:
337+
result = fn(values[0], values[1], values[2]);
338+
break;
339+
default:
340+
// Spreads are slow, so use direct branches for known number of params
341+
result = fn(...values);
342+
break;
343+
}
288344

289345
if (!jumps || (jumps && !result)) {
290-
this.pointer += modes.length;
346+
this.pointer += params;
291347
}
292348
}
293349

2019/19/part-two.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ const { input } = require('./input');
22
const { TractorBeam } = require('./tractor-beam');
33

44
let tractor_beam = new TractorBeam(input);
5-
// console.log(tractor_beam.partTwo());
6-
console.log(tractor_beam.partTwoOptimized());
5+
console.log(tractor_beam.partTwo());
6+
// console.log(tractor_beam.partTwoOptimized());

2019/19/tractor-beam.js

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { Computer } = require('./intcode-computer.js');
1+
const { Computer } = require('./intcode-computer-optimized.js');
22
const { InfiniteGrid } = require('./infinite-grid.js');
33

44
class TractorBeam {
@@ -192,22 +192,14 @@ class TractorBeam {
192192
// First, move down until we hit 0
193193
do {
194194
bottom_edge.y++;
195-
let computer = new Computer({
196-
memory: this.memory,
197-
inputs: [bottom_edge.x, bottom_edge.y],
198-
});
199-
[output] = computer.run();
195+
output = this.computeAt(bottom_edge.x, bottom_edge.y);
200196
this.grid.set(bottom_edge.x, bottom_edge.y, output);
201197
} while (output === 1);
202198

203199
// Then, move inward until we hit a `1`
204200
do {
205201
bottom_edge.x++;
206-
let computer = new Computer({
207-
memory: this.memory,
208-
inputs: [bottom_edge.x, bottom_edge.y],
209-
});
210-
[output] = computer.run();
202+
output = this.computeAt(bottom_edge.x, bottom_edge.y);
211203
this.grid.set(bottom_edge.x, bottom_edge.y, output);
212204
} while (output === 0);
213205
}
@@ -220,11 +212,7 @@ class TractorBeam {
220212
do {
221213
// Move right until we hit a `0`
222214
top_edge.x++;
223-
let computer = new Computer({
224-
memory: this.memory,
225-
inputs: [top_edge.x, top_edge.y],
226-
});
227-
[output] = computer.run();
215+
output = this.computeAt(top_edge.x, top_edge.y);
228216
this.grid.set(top_edge.x, top_edge.y, output);
229217
} while (output === 1);
230218
} while (allow_for_bottom_jumps && top_edge.y !== bottom_edge.y);
@@ -242,6 +230,7 @@ class TractorBeam {
242230
// The computer halts after every output, so we create a new one each time
243231
let computer = new Computer({ memory: this.memory, inputs: [x, y] });
244232
let [output] = computer.run();
233+
245234
return output;
246235
}
247236
}

0 commit comments

Comments
 (0)