From e73c352fa75716c116cd275f71b62a9c9a9d62ee Mon Sep 17 00:00:00 2001 From: Patrick Stevens Date: Sun, 11 Dec 2022 15:06:53 +0000 Subject: [PATCH] Speed up Day 12 by the power of MANAGING YOUR OWN MEMORY (#17) --- AdventOfCode2022.Test/Day11.fs | 11 +-- AdventOfCode2022/Day11.fs | 125 ++++++++++++++++++++++++++------- 2 files changed, 100 insertions(+), 36 deletions(-) diff --git a/AdventOfCode2022.Test/Day11.fs b/AdventOfCode2022.Test/Day11.fs index d9d1913..9b91e13 100644 --- a/AdventOfCode2022.Test/Day11.fs +++ b/AdventOfCode2022.Test/Day11.fs @@ -41,15 +41,6 @@ Monkey 3: let ``Part 1, given`` () = Day11.part1 (StringSplitEnumerator.make '\n' input) |> shouldEqual 10605 - [] - let ``Part 1, single round, given`` () = - let monkeys = Day11.parse (StringSplitEnumerator.make '\n' input) - let inspections = Array.zeroCreate monkeys.Length - - Day11.oneRoundDivThree monkeys inspections - - inspections |> shouldEqual [| 2 ; 4 ; 3 ; 5 |] - [] let ``Part 1`` () = let input = Assembly.readResource "Day11.txt" @@ -58,7 +49,7 @@ Monkey 3: [] - let ``Part 2, given 1`` () = + let ``Part 2, given`` () = Day11.part2 (StringSplitEnumerator.make '\n' input) |> shouldEqual 2713310158L [] diff --git a/AdventOfCode2022/Day11.fs b/AdventOfCode2022/Day11.fs index a08e3a1..5dbf57a 100644 --- a/AdventOfCode2022/Day11.fs +++ b/AdventOfCode2022/Day11.fs @@ -2,10 +2,14 @@ namespace AdventOfCode2022 open System.Collections.Generic open System +open Microsoft.FSharp.NativeInterop + #if DEBUG open Checked #endif +#nowarn "9" + [] type monkey @@ -14,8 +18,6 @@ module Day11 = type Monkey = { - Number : int - StartingItems : int64 ResizeArray OperationIsPlus : bool /// Negative is None Argument : int64 @@ -24,9 +26,10 @@ module Day11 = FalseCase : int } - let parse (lines : StringSplitEnumerator) : Monkey array = + let parse (memory : nativeptr) (lines : StringSplitEnumerator) : Monkey array * nativeptr[] * int[] = use mutable enum = lines let output = ResizeArray () + let startingItems = ResizeArray () while enum.MoveNext () do let monkey = @@ -160,9 +163,9 @@ module Day11 = if ifFalse = monkey then failwith "assumption broken: throws to self" + startingItems.Add startItems + { - Number = monkey - StartingItems = startItems OperationIsPlus = operationIsPlus Argument = arg TestDivisibleBy = test @@ -171,14 +174,36 @@ module Day11 = } |> output.Add - output.ToArray () + let totalItemCount = startingItems |> Seq.sumBy (fun x -> x.Count) - let oneRoundDivThree (monkeys : IReadOnlyList) (inspections : int64 array) = + let items = + Array.init + startingItems.Count + (fun i -> + for j in 0 .. startingItems.[i].Count - 1 do + NativePtr.write (NativePtr.add memory (totalItemCount * i + j)) startingItems.[i].[j] + + NativePtr.add memory (totalItemCount * i) + ) + + let counts = Array.init startingItems.Count (fun i -> startingItems.[i].Count) + + output.ToArray (), items, counts + + let oneRoundDivThree + (monkeys : IReadOnlyList) + (items : nativeptr[]) + (counts : nativeptr) + (inspections : int64 array) + = for i in 0 .. monkeys.Count - 1 do let monkey = monkeys.[i] - inspections.[i] <- inspections.[i] + int64 monkey.StartingItems.Count + let countsI = NativePtr.get counts i + inspections.[i] <- inspections.[i] + int64 countsI + + for worryIndex in 0 .. countsI - 1 do + let worry = NativePtr.get items.[i] worryIndex - for worry in monkey.StartingItems do let newWorry = let arg = match monkey.Argument with @@ -195,28 +220,55 @@ module Day11 = else monkey.FalseCase - monkeys.[target / 1].StartingItems.Add newWorry + let target = target / 1 + let targetCount = NativePtr.get counts target + NativePtr.write (NativePtr.add items.[target] targetCount) newWorry + NativePtr.write (NativePtr.add counts target) (targetCount + 1) - monkey.StartingItems.Clear () + NativePtr.write (NativePtr.add counts i) 0 + + let inline maxTwo (arr : int64 array) : struct (int64 * int64) = + let mutable best = max arr.[1] arr.[0] + let mutable secondBest = min arr.[1] arr.[0] + + for i in 2 .. arr.Length - 1 do + if arr.[i] > best then + secondBest <- best + best <- arr.[i] + elif arr.[i] > secondBest then + secondBest <- arr.[i] + + struct (secondBest, best) let part1 (lines : StringSplitEnumerator) : int64 = - let monkeys = parse lines + let memory = NativePtr.stackalloc 1000 + let monkeys, items, counts = parse memory lines + use counts = fixed counts let mutable inspections = Array.zeroCreate monkeys.Length for _round in 1..20 do - oneRoundDivThree monkeys inspections + oneRoundDivThree monkeys items counts inspections - inspections |> Array.sortInPlace - inspections.[inspections.Length - 1] * inspections.[inspections.Length - 2] + let struct (a, b) = maxTwo inspections - let inline oneRound (modulus : int64) (monkeys : Monkey array) (inspections : int64 array) = + a * b + + let oneRound + (modulus : int64) + (monkeys : Monkey array) + (items : nativeptr>) + (counts : nativeptr) + (inspections : nativeptr) + = for i in 0 .. monkeys.Length - 1 do let monkey = monkeys.[i] - inspections.[i] <- inspections.[i] + int64 monkey.StartingItems.Count + let entry = NativePtr.add inspections i + let countI = NativePtr.get counts i + NativePtr.write entry (NativePtr.read entry + int64 countI) - for worryIndex in 0 .. monkey.StartingItems.Count - 1 do - let worry = monkey.StartingItems.[worryIndex] + for worryIndex in 0 .. countI - 1 do + let worry = NativePtr.get (NativePtr.get items i) worryIndex let newWorry = let arg = @@ -234,21 +286,42 @@ module Day11 = else monkey.FalseCase - monkeys.[target / 1].StartingItems.Add newWorry + let target = target / 1 + NativePtr.write (NativePtr.add (NativePtr.get items target) (NativePtr.get counts target)) newWorry + NativePtr.write (NativePtr.add counts target) (NativePtr.get counts target + 1) - monkey.StartingItems.Clear () + NativePtr.write (NativePtr.add counts i) 0 + let inline unsafeMaxTwo (len : int) (arr : nativeptr) : struct (int64 * int64) = + let arr0 = NativePtr.read arr + let arr1 = NativePtr.get arr 1 + let mutable best = max arr0 arr1 + let mutable secondBest = min arr0 arr1 + + for i in 2 .. len - 1 do + let arrI = NativePtr.get arr i + + if arrI > best then + secondBest <- best + best <- arrI + elif arrI > secondBest then + secondBest <- arrI + + struct (secondBest, best) let part2 (lines : StringSplitEnumerator) : int64 = - let monkeys = parse lines + let memory = NativePtr.stackalloc 1000 + let monkeys, items, counts = parse memory lines + use counts = fixed counts + use items = fixed items - let mutable inspections = Array.zeroCreate monkeys.Length + let inspections = NativePtr.stackalloc monkeys.Length let modulus = (1L, monkeys) ||> Seq.fold (fun i monkey -> i * monkey.TestDivisibleBy) for _round in 1..10000 do - oneRound modulus monkeys inspections + oneRound modulus monkeys items counts inspections - inspections |> Array.sortInPlace - inspections.[inspections.Length - 1] * inspections.[inspections.Length - 2] + let struct (a, b) = unsafeMaxTwo monkeys.Length inspections + a * b