moonlight-triangulation-0.1.0.0: bench/dual/Moonlight/Triangulation/DualBench.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NumericUnderscores #-}
-- | The dual side: Delaunay-hierarchy hints against an unhinted walk, and
-- natural-neighbour interpolation over a reused workspace. Both report
-- allocation, because the claim in each case is about work avoided rather than
-- time taken.
module Moonlight.Triangulation.DualBench (benchmarks) where
import BenchSupport (randomPoints, requireRight, timedValue)
import Control.DeepSeq (force)
import Control.Exception (evaluate)
import Control.Monad.ST (stToIO)
import qualified Data.Vector as V
import GHC.Exts (RealWorld)
import GHC.Stats (RTSStats (allocated_bytes), getRTSStats, getRTSStatsEnabled)
import Moonlight.Triangulation
import Moonlight.Triangulation.HintGenerator
( HierarchyHint
, buildHierarchyHint
, defaultHierarchyBranchFactor
, hierarchyHint
, hierarchyLevelCount
, hierarchyVertexCount
)
import Moonlight.Triangulation.Interpolation
( NaturalNeighborWorkspace
, interpolateNaturalNeighbor
, newNaturalNeighborWorkspace
, workspaceBytes
)
import System.Mem (performGC)
benchmarks :: IO ()
benchmarks = do
benchmarkHierarchy 20_000 5_000
benchmarkSibson 10_000 5_000
benchmarkHierarchy :: Int -> Int -> IO ()
benchmarkHierarchy pointCount queryCount = do
built <- requireRight (delaunay unitElementDefaults (V.fromList (randomPoints 0x123456789abcdef pointCount)))
queries <-
requireRight
(traverse mkQueryPoint (V.fromList (take queryCount (randomPoints 0x3141592653589793 queryCount))))
let triangulation = buildTriangulation built
hierarchy <- requireRight (buildHierarchyHint defaultHierarchyBranchFactor triangulation)
(_, baselineSteps) <- timedValue "nearest/no-hierarchy" (evaluate (force (walkTotal triangulation Nothing queries)))
(_, hierarchySteps) <- timedValue "nearest/delaunay-hierarchy" (evaluate (force (walkHierarchyTotal triangulation hierarchy queries)))
putStrLn ("hierarchy-levels: " <> show (hierarchyLevelCount hierarchy))
putStrLn ("hierarchy-vertices: " <> show (hierarchyVertexCount hierarchy))
putStrLn ("nearest-walk-steps/no-hierarchy: " <> show baselineSteps)
putStrLn ("nearest-walk-steps/hierarchy: " <> show hierarchySteps)
where
walkTotal
:: DelaunayTriangulation (Point)
-> Maybe VertexId
-> V.Vector (QueryPoint)
-> (Int, Int)
walkTotal triangulation hint queries = V.foldl' step (0 :: Int, 0 :: Int) queries
where
step (!count, !steps) query =
case nearestNeighbor triangulation hint query of
Nothing -> (count, steps)
Just (_, stats) -> (count + 1, steps + nearestWalkSteps stats)
walkHierarchyTotal
:: DelaunayTriangulation (Point)
-> HierarchyHint
-> V.Vector (QueryPoint)
-> (Int, Int)
walkHierarchyTotal triangulation hierarchy queries = V.foldl' step (0 :: Int, 0 :: Int) queries
where
step (!count, !steps) query =
let hint = case hierarchyHint hierarchy query of
Just (VertexHint vertex) -> Just vertex
_ -> Nothing
in case nearestNeighbor triangulation hint query of
Nothing -> (count, steps)
Just (_, stats) -> (count + 1, steps + nearestWalkSteps stats)
benchmarkSibson :: Int -> Int -> IO ()
benchmarkSibson pointCount queryCount = do
built <- requireRight (delaunay unitElementDefaults (V.fromList (randomPoints 0x8cb92baa3f3d8dd7 pointCount)))
queries <-
requireRight
(traverse mkQueryPoint (V.fromList (take queryCount (randomPoints 0xdb4f0b9175ae2165 queryCount))))
let triangulation = buildTriangulation built
height vertex =
let Point x y = vertexPoint triangulation vertex
in x * x + 0.5 * y
workspace <- stToIO (newNaturalNeighborWorkspace triangulation)
putStrLn ("sibson-workspace-bytes: " <> show (workspaceBytes workspace))
statsEnabled <- getRTSStatsEnabled
if statsEnabled then performGC else pure ()
before <- if statsEnabled then Just <$> getRTSStats else pure Nothing
_ <- timedValue "sibson/reused-workspace" (queryLoop workspace height queries)
after <- if statsEnabled then Just <$> getRTSStats else pure Nothing
case (before, after) of
(Just left, Just right) -> do
let allocated = allocated_bytes right - allocated_bytes left
perQuery = fromIntegral allocated / fromIntegral queryCount :: Double
putStrLn ("sibson-allocated-bytes-total: " <> show allocated)
putStrLn ("sibson-allocated-bytes/query: " <> show perQuery)
_ -> putStrLn "sibson allocation counters unavailable; run with +RTS -T"
where
queryLoop
:: NaturalNeighborWorkspace RealWorld 'Unconstrained (Point) () () ()
-> (VertexId -> Double)
-> V.Vector (QueryPoint)
-> IO Double
queryLoop workspace height queries = go 0 0
where
!count = V.length queries
go !index !total
| index >= count = evaluate total
| otherwise = do
(value, _) <- stToIO (interpolateNaturalNeighbor height workspace Nothing (queries V.! index))
go (index + 1) (total + maybe 0 id value)