Browse Source

Intcode optimisations.

- Work out parameters inline and pass them, to save overhead of
  calling a function
- Reduce number of cases we check for memory bounds, and don't
  add quite so much each time
- Change CSV reading function to just use ASCII chars not a full
  unicode decode
master
Chris Smith 4 years ago
parent
commit
9247dc5e13
Signed by: Chris Smith <chris@chameth.com> GPG Key ID: 3A2D4BBDC4A3C9A9
4 changed files with 91 additions and 90 deletions
  1. 1
    0
      09/.intcode
  2. 4
    10
      common/io.go
  3. 39
    26
      intcode/ops.go
  4. 47
    54
      intcode/vm.go

+ 1
- 0
09/.intcode View File

@@ -0,0 +1 @@
1
+201912152221

+ 4
- 10
common/io.go View File

@@ -3,8 +3,6 @@ package common
3 3
 import (
4 4
 	"bufio"
5 5
 	"os"
6
-	"unicode"
7
-	"unicode/utf8"
8 6
 )
9 7
 
10 8
 // ReadFileAsInts reads all lines from the given path and returns them in a slice of ints.
@@ -84,19 +82,15 @@ func scanByCommas(data []byte, atEOF bool) (advance int, token []byte, err error
84 82
 	// Skip leading spaces.
85 83
 	start := 0
86 84
 	for width := 0; start < len(data); start += width {
87
-		var r rune
88
-		r, width = utf8.DecodeRune(data[start:])
89
-		if !unicode.IsSpace(r) {
85
+		if data[start] != ' ' {
90 86
 			break
91 87
 		}
92 88
 	}
93 89
 
94 90
 	// Scan until comma, marking end of word.
95
-	for width, i := 0, start; i < len(data); i += width {
96
-		var r rune
97
-		r, width = utf8.DecodeRune(data[i:])
98
-		if r == ',' || unicode.IsSpace(r) {
99
-			return i + width, data[start:i], nil
91
+	for i := start; i < len(data); i++ {
92
+		if data[i] == ',' || data[i] == ' ' || data[i] == '\r' || data[i] == '\n' {
93
+			return i + 1, data[start:i], nil
100 94
 		}
101 95
 	}
102 96
 	// If we're at EOF, we have a final, non-empty, non-terminated word. Return it.

+ 39
- 26
intcode/ops.go View File

@@ -1,7 +1,7 @@
1 1
 package intcode
2 2
 
3 3
 // opcodeFunc is a function that describes an opcode implemented in the VM.
4
-type opcodeFunc = func(vm *VirtualMachine)
4
+type opcodeFunc = func(vm *VirtualMachine, arg1, arg2, arg3 *int)
5 5
 
6 6
 var opcodes = [100]opcodeFunc{
7 7
 	1:  addOpcode,
@@ -16,45 +16,58 @@ var opcodes = [100]opcodeFunc{
16 16
 	99: haltOpcode,
17 17
 }
18 18
 
19
+var opcodeArity = [100]int{
20
+	1:  3,
21
+	2:  3,
22
+	3:  1,
23
+	4:  1,
24
+	5:  2,
25
+	6:  2,
26
+	7:  3,
27
+	8:  3,
28
+	9:  1,
29
+	99: 0,
30
+}
31
+
19 32
 // addOpcode takes the values specified by args 1 and 2, adds them together, and stores at the memory address given
20 33
 // by arg 3.
21
-func addOpcode(vm *VirtualMachine) {
22
-	*vm.arg(2) = *vm.arg(0) + *vm.arg(1)
34
+func addOpcode(vm *VirtualMachine, arg1, arg2, arg3 *int) {
35
+	*arg3 = *arg1 + *arg2
23 36
 	vm.ip += 4
24 37
 }
25 38
 
26 39
 // mulOpcode takes the values specified by args 1 and 2, multiplies them together, and stores at the memory address
27 40
 // given by arg 3.
28
-func mulOpcode(vm *VirtualMachine) {
29
-	*vm.arg(2) = *vm.arg(0) * *vm.arg(1)
41
+func mulOpcode(vm *VirtualMachine, arg1, arg2, arg3 *int) {
42
+	*arg3 = *arg1 * *arg2
30 43
 	vm.ip += 4
31 44
 }
32 45
 
33 46
 // readOpCode reads a value from the input stream and stores it at the memory address given by arg 1.
34
-func readOpCode(vm *VirtualMachine) {
35
-	*vm.arg(0) = <-vm.Input
47
+func readOpCode(vm *VirtualMachine, arg1, _, _ *int) {
48
+	*arg1 = <-vm.Input
36 49
 	vm.ip += 2
37 50
 }
38 51
 
39 52
 // writeOpCode writes the value specified by the first argument to the output stream.
40
-func writeOpCode(vm *VirtualMachine) {
41
-	vm.Output <- *vm.arg(0)
53
+func writeOpCode(vm *VirtualMachine, arg1, _, _ *int) {
54
+	vm.Output <- *arg1
42 55
 	vm.ip += 2
43 56
 }
44 57
 
45 58
 // jumpIfTrueOpCode checks if the first argument is not zero, and if so jumps to the second argument.
46
-func jumpIfTrueOpCode(vm *VirtualMachine) {
47
-	if *vm.arg(0) != 0 {
48
-		vm.ip = *vm.arg(1)
59
+func jumpIfTrueOpCode(vm *VirtualMachine, arg1, arg2, _ *int) {
60
+	if *arg1 != 0 {
61
+		vm.ip = *arg2
49 62
 	} else {
50 63
 		vm.ip += 3
51 64
 	}
52 65
 }
53 66
 
54 67
 // jumpIfFalseOpCode checks if the first argument is zero, and if so jumps to the second argument.
55
-func jumpIfFalseOpCode(vm *VirtualMachine) {
56
-	if *vm.arg(0) == 0 {
57
-		vm.ip = *vm.arg(1)
68
+func jumpIfFalseOpCode(vm *VirtualMachine, arg1, arg2, _ *int) {
69
+	if *arg1 == 0 {
70
+		vm.ip = *arg2
58 71
 	} else {
59 72
 		vm.ip += 3
60 73
 	}
@@ -62,33 +75,33 @@ func jumpIfFalseOpCode(vm *VirtualMachine) {
62 75
 
63 76
 // lessThanOpCode checks if the first argument is less than the second, and stores the result at the address given
64 77
 // by the third argument.
65
-func lessThanOpCode(vm *VirtualMachine) {
66
-	if *vm.arg(0) < *vm.arg(1) {
67
-		*vm.arg(2) = 1
78
+func lessThanOpCode(vm *VirtualMachine, arg1, arg2, arg3 *int) {
79
+	if *arg1 < *arg2 {
80
+		*arg3 = 1
68 81
 	} else {
69
-		*vm.arg(2) = 0
82
+		*arg3 = 0
70 83
 	}
71 84
 	vm.ip += 4
72 85
 }
73 86
 
74 87
 // equalsOpCode checks if the first argument is equal to the second, and stores the result at the address given
75 88
 // by the third argument.
76
-func equalsOpCode(vm *VirtualMachine) {
77
-	if *vm.arg(0) == *vm.arg(1) {
78
-		*vm.arg(2) = 1
89
+func equalsOpCode(vm *VirtualMachine, arg1, arg2, arg3 *int) {
90
+	if *arg1 == *arg2 {
91
+		*arg3 = 1
79 92
 	} else {
80
-		*vm.arg(2) = 0
93
+		*arg3 = 0
81 94
 	}
82 95
 	vm.ip += 4
83 96
 }
84 97
 
85 98
 // relativeBaseOffsetOpCode increases the relative base by the given argument.
86
-func relativeBaseOffsetOpCode(vm *VirtualMachine) {
87
-	vm.rb += *vm.arg(0)
99
+func relativeBaseOffsetOpCode(vm *VirtualMachine, arg1, _, _ *int) {
100
+	vm.rb += *arg1
88 101
 	vm.ip += 2
89 102
 }
90 103
 
91 104
 // haltOpcode halts the VM and takes no arguments.
92
-func haltOpcode(vm *VirtualMachine) {
105
+func haltOpcode(vm *VirtualMachine, _, _, _ *int) {
93 106
 	vm.Halted = true
94 107
 }

+ 47
- 54
intcode/vm.go View File

@@ -1,15 +1,21 @@
1 1
 package intcode
2 2
 
3
+type parameterMode int
4
+
5
+const (
6
+	position  parameterMode = 0
7
+	immediate parameterMode = 1
8
+	relative  parameterMode = 2
9
+)
10
+
3 11
 // VirtualMachine is an IntCode virtual machine.
4 12
 type VirtualMachine struct {
5
-	ip             int
6
-	rb             int
7
-	parameterModes uint8
8
-	relativeModes  uint8
9
-	Memory         []int
10
-	Halted         bool
11
-	Input          chan int
12
-	Output         chan int
13
+	ip     int
14
+	rb     int
15
+	Memory []int
16
+	Halted bool
17
+	Input  chan int
18
+	Output chan int
13 19
 }
14 20
 
15 21
 // NewVirtualMachine creates a new IntCode virtual machine, initialised to the given slice of memory.
@@ -30,64 +36,51 @@ func (vm *VirtualMachine) Clone() *VirtualMachine {
30 36
 	copy(memory, vm.Memory)
31 37
 
32 38
 	return &VirtualMachine{
33
-		ip:             vm.ip,
34
-		rb:             vm.rb,
35
-		parameterModes: vm.parameterModes,
36
-		relativeModes:  vm.relativeModes,
37
-		Memory:         memory,
38
-		Halted:         vm.Halted,
39
-		Input:          make(chan int, 1),
40
-		Output:         make(chan int, 1),
41
-	}
42
-}
43
-
44
-// arg Returns the value of the given argument for the current instruction.
45
-func (vm *VirtualMachine) arg(pos int) *int {
46
-	mask := uint8(1) << uint8(pos)
47
-	if vm.parameterModes&mask == mask {
48
-		// Parameter mode - the value of the argument is just treated as an int
49
-		for vm.ip+1+pos > len(vm.Memory) {
50
-			vm.Memory = append(vm.Memory, make([]int, 1024)...)
51
-		}
52
-
53
-		return &vm.Memory[vm.ip+1+pos]
54
-	} else if vm.relativeModes&mask == mask {
55
-		// Relative mode - the value of the argument is treated as a memory offset from the relative base
56
-		for vm.ip+1+pos > len(vm.Memory) || vm.rb+vm.Memory[vm.ip+1+pos] > len(vm.Memory) {
57
-			vm.Memory = append(vm.Memory, make([]int, 1024)...)
58
-		}
59
-
60
-		return &vm.Memory[vm.rb+vm.Memory[vm.ip+1+pos]]
61
-	} else {
62
-		// Position mode - the value of the argument is treated as a memory offset from the start of the memory
63
-		for vm.ip+1+pos > len(vm.Memory) || vm.Memory[vm.ip+1+pos] > len(vm.Memory) {
64
-			vm.Memory = append(vm.Memory, make([]int, 1024)...)
65
-		}
66
-
67
-		return &vm.Memory[vm.Memory[vm.ip+1+pos]]
39
+		ip:     vm.ip,
40
+		rb:     vm.rb,
41
+		Memory: memory,
42
+		Halted: vm.Halted,
43
+		Input:  make(chan int, 1),
44
+		Output: make(chan int, 1),
68 45
 	}
69 46
 }
70 47
 
71 48
 // Run repeatedly executes instructions until the VM halts.
72 49
 func (vm *VirtualMachine) Run() {
50
+	var args [3]*int
73 51
 	for !vm.Halted {
74 52
 		instruction := vm.Memory[vm.ip]
75 53
 		opcode := instruction % 100
76 54
 
77
-		vm.parameterModes = 0
78
-		vm.relativeModes = 0
79
-		mask := uint8(1)
80
-		for i := instruction / 100; i > 0; i /= 10 {
81
-			mode := i % 10
82
-			if mode == 1 {
83
-				vm.parameterModes = vm.parameterModes | mask
84
-			} else if mode == 2 {
85
-				vm.relativeModes = vm.relativeModes | mask
55
+		param := instruction / 100
56
+		for i := 0; i < opcodeArity[opcode]; i++ {
57
+
58
+			switch parameterMode(param % 10) {
59
+
60
+			// The argument is the actual value
61
+			case immediate:
62
+				args[i] = &vm.Memory[vm.ip+1+i]
63
+
64
+			// The argument is a memory reference
65
+			case position:
66
+				for vm.Memory[vm.ip+1+i] >= len(vm.Memory) {
67
+					vm.Memory = append(vm.Memory, make([]int, 128)...)
68
+				}
69
+				args[i] = &vm.Memory[vm.Memory[vm.ip+1+i]]
70
+
71
+			// The argument is a memory reference offset by the relative base
72
+			case relative:
73
+				for vm.rb+vm.Memory[vm.ip+1+i] >= len(vm.Memory) {
74
+					vm.Memory = append(vm.Memory, make([]int, 128)...)
75
+				}
76
+				args[i] = &vm.Memory[vm.rb+vm.Memory[vm.ip+1+i]]
77
+
86 78
 			}
87
-			mask = mask << 1
79
+
80
+			param /= 10
88 81
 		}
89 82
 
90
-		opcodes[opcode](vm)
83
+		opcodes[opcode](vm, args[0], args[1], args[2])
91 84
 	}
92 85
 	if vm.Output != nil {
93 86
 		close(vm.Output)

Loading…
Cancel
Save