Browse Source

Various intcode optimisations

- Store opcodes in an array rather than a map for faster lookup
  (this means storing them as interface{} as otherwise the types
  form a circular dependency, which is icky but worth the
  performance boost).
- Don't dynamically create an 'Arg' function every step of the vm.
  This was *super* expensive.
- Calculate a bitmask of the argument modes instead of relying
  on on-demand div-modding.
- Only buffer a single value in the I/O buffers to reduce the
  size of allocations.
master
Chris Smith 4 years ago
parent
commit
764d3e886e
Signed by: Chris Smith <chris@chameth.com> GPG Key ID: 3A2D4BBDC4A3C9A9
3 changed files with 46 additions and 45 deletions
  1. 2
    2
      05/main.go
  2. 19
    22
      intcode/ops.go
  3. 25
    21
      intcode/vm.go

+ 2
- 2
05/main.go View File

@@ -24,11 +24,11 @@ func main() {
24 24
 
25 25
 	vm := intcode.NewVirtualMachine(memory)
26 26
 	vm.Input <- 1
27
-	vm.Run()
27
+	go vm.Run()
28 28
 	fmt.Println(last(vm.Output))
29 29
 
30 30
 	vm.Reset(input)
31 31
 	vm.Input <- 5
32
-	vm.Run()
32
+	go vm.Run()
33 33
 	fmt.Println(last(vm.Output))
34 34
 }

+ 19
- 22
intcode/ops.go View File

@@ -1,50 +1,47 @@
1 1
 package intcode
2 2
 
3
-// ArgFunc provides the value of an argument for an opcode
4
-type ArgFunc = func(pos int) int
5
-
6 3
 // OpcodeFunc is a function that describes an opcode implemented in the VM.
7
-type OpcodeFunc = func(vm *VirtualMachine, param ArgFunc)
4
+type OpcodeFunc = func(vm *VirtualMachine)
8 5
 
9 6
 // AddOpcode takes the values specified by args 1 and 2, adds them together, and stores at the memory address given
10 7
 // by arg 3.
11
-func AddOpcode(vm *VirtualMachine, arg ArgFunc) {
12
-	vm.Memory[vm.Memory[vm.ip+3]] = arg(0) + arg(1)
8
+func AddOpcode(vm *VirtualMachine) {
9
+	vm.Memory[vm.Memory[vm.ip+3]] = vm.arg(0) + vm.arg(1)
13 10
 	vm.ip += 4
14 11
 }
15 12
 
16 13
 // MulOpcode takes the values specified by args 1 and 2, multiplies them together, and stores at the memory address
17 14
 // given by arg 3.
18
-func MulOpcode(vm *VirtualMachine, arg ArgFunc) {
19
-	vm.Memory[vm.Memory[vm.ip+3]] = arg(0) * arg(1)
15
+func MulOpcode(vm *VirtualMachine) {
16
+	vm.Memory[vm.Memory[vm.ip+3]] = vm.arg(0) * vm.arg(1)
20 17
 	vm.ip += 4
21 18
 }
22 19
 
23 20
 // ReadOpCode reads a value from the input stream and stores it at the memory address given by arg 1.
24
-func ReadOpCode(vm *VirtualMachine, arg ArgFunc) {
21
+func ReadOpCode(vm *VirtualMachine) {
25 22
 	vm.Memory[vm.Memory[vm.ip+1]] = <-vm.Input
26 23
 	vm.ip += 2
27 24
 }
28 25
 
29 26
 // WriteOpCode writes the value specified by the first argument to the output stream.
30
-func WriteOpCode(vm *VirtualMachine, arg ArgFunc) {
31
-	vm.Output <- arg(0)
27
+func WriteOpCode(vm *VirtualMachine) {
28
+	vm.Output <- vm.arg(0)
32 29
 	vm.ip += 2
33 30
 }
34 31
 
35 32
 // JumpIfTrueOpCode checks if the first argument is not zero, and if so jumps to the second argument.
36
-func JumpIfTrueOpCode(vm *VirtualMachine, arg ArgFunc) {
37
-	if arg(0) != 0 {
38
-		vm.ip = arg(1)
33
+func JumpIfTrueOpCode(vm *VirtualMachine) {
34
+	if vm.arg(0) != 0 {
35
+		vm.ip = vm.arg(1)
39 36
 	} else {
40 37
 		vm.ip += 3
41 38
 	}
42 39
 }
43 40
 
44 41
 // JumpIfFalseOpCode checks if the first argument is zero, and if so jumps to the second argument.
45
-func JumpIfFalseOpCode(vm *VirtualMachine, arg ArgFunc) {
46
-	if arg(0) == 0 {
47
-		vm.ip = arg(1)
42
+func JumpIfFalseOpCode(vm *VirtualMachine) {
43
+	if vm.arg(0) == 0 {
44
+		vm.ip = vm.arg(1)
48 45
 	} else {
49 46
 		vm.ip += 3
50 47
 	}
@@ -52,8 +49,8 @@ func JumpIfFalseOpCode(vm *VirtualMachine, arg ArgFunc) {
52 49
 
53 50
 // LessThanOpCode checks if the first argument is less than the second, and stores the result at the address given
54 51
 // by the third argument.
55
-func LessThanOpCode(vm *VirtualMachine, arg ArgFunc) {
56
-	if arg(0) < arg(1) {
52
+func LessThanOpCode(vm *VirtualMachine) {
53
+	if vm.arg(0) < vm.arg(1) {
57 54
 		vm.Memory[vm.Memory[vm.ip+3]] = 1
58 55
 	} else {
59 56
 		vm.Memory[vm.Memory[vm.ip+3]] = 0
@@ -63,8 +60,8 @@ func LessThanOpCode(vm *VirtualMachine, arg ArgFunc) {
63 60
 
64 61
 // EqualsOpCode checks if the first argument is equal to the second, and stores the result at the address given
65 62
 // by the third argument.
66
-func EqualsOpCode(vm *VirtualMachine, arg ArgFunc) {
67
-	if arg(0) == arg(1) {
63
+func EqualsOpCode(vm *VirtualMachine) {
64
+	if vm.arg(0) == vm.arg(1) {
68 65
 		vm.Memory[vm.Memory[vm.ip+3]] = 1
69 66
 	} else {
70 67
 		vm.Memory[vm.Memory[vm.ip+3]] = 0
@@ -73,6 +70,6 @@ func EqualsOpCode(vm *VirtualMachine, arg ArgFunc) {
73 70
 }
74 71
 
75 72
 // HaltOpcode halts the VM and takes no arguments.
76
-func HaltOpcode(vm *VirtualMachine, arg ArgFunc) {
73
+func HaltOpcode(vm *VirtualMachine) {
77 74
 	vm.Halted = true
78 75
 }

+ 25
- 21
intcode/vm.go View File

@@ -1,14 +1,10 @@
1 1
 package intcode
2 2
 
3
-import (
4
-	"fmt"
5
-	"math"
6
-)
7
-
8 3
 // VirtualMachine is an IntCode virtual machine.
9 4
 type VirtualMachine struct {
10 5
 	ip      int
11
-	opcodes map[int]OpcodeFunc
6
+	modes   uint8
7
+	opcodes [100]interface{}
12 8
 	Memory  []int
13 9
 	Halted  bool
14 10
 	Input   chan int
@@ -22,9 +18,9 @@ func NewVirtualMachine(memory []int) *VirtualMachine {
22 18
 		ip:     0,
23 19
 		Memory: memory,
24 20
 		Halted: false,
25
-		Input:  make(chan int, 100),
26
-		Output: make(chan int, 100),
27
-		opcodes: map[int]OpcodeFunc{
21
+		Input:  make(chan int, 1),
22
+		Output: make(chan int, 1),
23
+		opcodes: [100]interface{}{
28 24
 			1:  AddOpcode,
29 25
 			2:  MulOpcode,
30 26
 			3:  ReadOpCode,
@@ -38,23 +34,31 @@ func NewVirtualMachine(memory []int) *VirtualMachine {
38 34
 	}
39 35
 }
40 36
 
37
+func (vm *VirtualMachine) arg(pos int) int {
38
+	mask := uint8(1) << uint8(pos)
39
+	if vm.modes&mask == mask {
40
+		return vm.Memory[vm.ip+1+pos]
41
+	} else {
42
+		return vm.Memory[vm.Memory[vm.ip+1+pos]]
43
+	}
44
+}
45
+
41 46
 // Run repeatedly executes instructions until the VM halts.
42 47
 func (vm *VirtualMachine) Run() {
43 48
 	for !vm.Halted {
44 49
 		instruction := vm.Memory[vm.ip]
45 50
 		opcode := instruction % 100
46 51
 
47
-		vm.opcodes[opcode](vm, func(pos int) int {
48
-			mode := (instruction / int(math.Pow10(2+pos))) % 10
49
-			switch mode {
50
-			case 0:
51
-				return vm.Memory[vm.Memory[vm.ip+1+pos]]
52
-			case 1:
53
-				return vm.Memory[vm.ip+1+pos]
54
-			default:
55
-				panic(fmt.Sprintf("Unknown parameter mode: %d", mode))
52
+		vm.modes = 0
53
+		mask := uint8(1)
54
+		for i := instruction / 100; i > 0; i /= 10 {
55
+			if i%10 == 1 {
56
+				vm.modes = vm.modes | mask
56 57
 			}
57
-		})
58
+			mask = mask << 1
59
+		}
60
+
61
+		vm.opcodes[opcode].(OpcodeFunc)(vm)
58 62
 	}
59 63
 	close(vm.Input)
60 64
 	close(vm.Output)
@@ -65,6 +69,6 @@ func (vm *VirtualMachine) Reset(memory []int) {
65 69
 	copy(vm.Memory, memory)
66 70
 	vm.ip = 0
67 71
 	vm.Halted = false
68
-	vm.Input = make(chan int, 100)
69
-	vm.Output = make(chan int, 100)
72
+	vm.Input = make(chan int, 1)
73
+	vm.Output = make(chan int, 1)
70 74
 }

Loading…
Cancel
Save