Skip to content

Commit b72ec69

Browse files
committed
Finishes optimization
Wow, went from 17 second runtime to 2 seconds!
1 parent f459391 commit b72ec69

File tree

1 file changed

+49
-13
lines changed

1 file changed

+49
-13
lines changed

2019/19/intcode-computer.js

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class Computer {
6262
name: INP,
6363
realName: 'INP',
6464
params: 1,
65-
fn: a => {
65+
fn: (a) => {
6666
this.memory[a] = this.inputs.shift();
6767
if (this.replenish_input !== undefined) {
6868
this.inputs.push(this.replenish_input);
@@ -75,14 +75,14 @@ class Computer {
7575
name: OUT,
7676
realName: 'OUT',
7777
params: 1,
78-
fn: a => this.output(a),
78+
fn: (a) => this.output(a),
7979
},
8080

8181
[ARB]: {
8282
name: ARB,
8383
realName: 'ARB',
8484
params: 1,
85-
fn: a => (this.relative_base += a),
85+
fn: (a) => (this.relative_base += a),
8686
},
8787

8888
[STP]: {
@@ -142,8 +142,26 @@ class Computer {
142142
};
143143

144144
const ops_list = Object.values(this.OPS);
145-
const max_params = Math.max(...ops_list.map(v => v.params));
145+
const max_params = Math.max(...ops_list.map((v) => v.params));
146146
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+
*/
147165
for (let op of ops_list) {
148166
op.modes = shared_modes;
149167
op.values = Array(op.params).fill(0);
@@ -173,7 +191,6 @@ class Computer {
173191
}
174192

175193
parseOp() {
176-
let start = new Date();
177194
let temp_op = String(this.memory[this.pointer]).padStart(2, '0');
178195

179196
// "The opcode is a two-digit number based only on the ones and tens digit of the value, that is, the opcode is the rightmost two digits of the first value in an instruction"
@@ -194,8 +211,6 @@ class Computer {
194211
op.modes[Math.abs(i - op.params + 1)] = full_op[i];
195212
}
196213

197-
let end = new Date();
198-
this.parseOpTime += (end - start);
199214
return op;
200215
}
201216

@@ -299,7 +314,33 @@ class Computer {
299314
}
300315

301316
// If result is `true`, we moved the pointer
302-
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+
}
303344

304345
if (!jumps || (jumps && !result)) {
305346
this.pointer += params;
@@ -314,11 +355,6 @@ class Computer {
314355
get _() {
315356
return this.memory.slice(Math.max(0, this.pointer - 1), this.pointer + 8);
316357
}
317-
318-
logParseOpDuration() {
319-
const million = BigInt(1e6);
320-
console.log(`parseOp took ${parseOpTime / million} ms`);
321-
}
322358
}
323359

324360
module.exports = {

0 commit comments

Comments
 (0)