@@ -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
0 commit comments