Browse Source

Further performance improvements

master
Chris Smith 5 years ago
parent
commit
6e97ab6839
1 changed files with 37 additions and 18 deletions
  1. 37
    18
      day09.nim

+ 37
- 18
day09.nim View File

@@ -1,27 +1,28 @@
1 1
 import strscans
2 2
 
3
+# For performance, we only use pointers to Marbles (and manually allocate)
4
+# memory required for them. This stops the GC tracking them, saving a fair
5
+# chunk of time. Removed marbles will leak, but it doesn't really matter for
6
+# the scope of this program.
7
+
3 8
 type
4
-    Marble = object
5
-        next, prev: ptr Marble
9
+    Marble = ptr object
10
+        next: Marble
6 11
         value: int32
7 12
 
8
-proc insertAfter(node: ptr Marble, value: int) {.inline.} =
9
-    var newNode = cast[ptr Marble](alloc0(sizeof(Marble)))
13
+proc insertAfter(node: Marble, value: int) {.inline.} =
14
+    var newNode = cast[Marble](alloc0(sizeof(Marble)))
10 15
     newNode.value = cast[int32](value)
11 16
     newNode.next = node.next
12
-    newNode.prev = node
13
-    newNode.next.prev = newNode
14
-    newNode.prev.next = newNode
17
+    node.next = newNode
15 18
 
16
-proc remove(node: ptr Marble) {.inline.} =
17
-    node.prev.next = node.next
18
-    node.next.prev = node.prev
19
+proc removeNext(node: Marble) {.inline.} =
20
+    node.next = node.next.next
19 21
 
20
-proc newSingleNode(value: int): ptr Marble =
21
-    result = cast[ptr Marble](alloc0(sizeof(Marble)))
22
+proc newSingleNode(value: int): Marble =
23
+    result = cast[Marble](alloc0(sizeof(Marble)))
22 24
     result.value = cast[int32](value)
23 25
     result.next = result
24
-    result.prev = result
25 26
 
26 27
 var
27 28
     input = readFile("data/09.txt")
@@ -31,25 +32,43 @@ var
31 32
 if not input.scanf("$i players; last marble is worth $i points", players, marbles):
32 33
     raise newException(Defect,  "Invalid input line: " & input)
33 34
 
35
+# Instead of using a doubly-linked list, we keep a current pointer and a
36
+# separate one trailing behind it. The trail will expand to 8 marbles and is
37
+# used when a multiple of 23 is played (so we can remove the N-7th marble). The
38
+# trail then catches up to the current pointer and drifts back to 8 again over
39
+# the next few moves. This saves a 64 bit memory allocation per marble, which
40
+# gives a small but noticable speed bump.
41
+
34 42
 var
35 43
     player = 0
36 44
     scores = newSeq[int](players)
37 45
     current = newSingleNode(0)
46
+    currentTrail = current
47
+    currentTrailDrift = 0
38 48
     specialCountdown = 23
39 49
     hundredMarbles = marbles * 100
40 50
 
41 51
 for i in 1 .. hundredMarbles:
52
+    # Instead of testing each marble number to see if it's a multiple of 23, we
53
+    # keep a counter that we just loop over and over again.
42 54
     specialCountdown.dec
43 55
     if specialCountdown == 0:
44
-        specialCountdown = 23
56
+        # The current player is only relevant when a 23nth marble is played, so
57
+        # we can just update the player here instead of every turn.
45 58
         player = (player + 23) mod players
46
-        current = current.prev.prev.prev.prev.prev.prev.prev
47
-        scores[player] += i + current.value
48
-        current.remove
49
-        current = current.next
59
+        scores[player] += i + currentTrail.next.value
60
+        currentTrail.removeNext
61
+        current = currentTrail.next
62
+        currentTrail = current
63
+        currentTrailDrift = 0
64
+        specialCountdown = 23
50 65
     else:
51 66
         current.next.insertAfter(i)
52 67
         current = current.next.next
68
+        if currentTrailDrift == 8:
69
+            currentTrail = currentTrail.next.next
70
+        else:
71
+            currentTrailDrift += 2
53 72
 
54 73
     if i == marbles or i == hundredMarbles:
55 74
         echo scores.max