packages feed

pure-noise-0.2.2.0: bench/Bench.hs

import BenchLib
import Data.Massiv.Array qualified as MA
import Data.Typeable
import Data.Vector.Unboxed qualified as U
import Numeric.Noise
import System.Random.Stateful (StatefulGen, UniformRange, newAtomicGenM, newStdGen, uniformRM)

main :: IO ()
main = do
  let sz = 1_000_000
      octaves = 8
      massivW = 1_000
      massivH = 1_000
  defaultMain
    [ bgroup
        "2D"
        ( baseline2 sz
            <> benchPerlin2 octaves sz
            <> benchOpenSimplex2 octaves sz
            <> benchOpenSimplexSmooth2 octaves sz
            <> benchValue2 octaves sz
            <> benchValueCubic2 octaves sz
            <> benchCombo2 octaves sz
            <> benchCellular2 octaves sz
        )
    , bgroup
        "3D"
        ( baseline3 sz
            <> benchPerlin3 octaves sz
            <> benchOpenSimplex3 octaves sz
            <> benchSuperSimplex3 octaves sz
            <> benchValue3 octaves sz
            <> benchValueCubic3 octaves sz
            <> benchCellular3 sz
        )
    , bgroup
        "2D massiv"
        ( benchMassivBase2 massivW massivH
            <> benchMassivFractal2 octaves massivW massivH
        )
    ]

label :: (Typeable a) => String -> Int -> Proxy a -> String
label lbl sz px =
  let lbl' = case lbl of
        "" -> ""
        v -> v <> ": "
   in lbl' <> showsTypeRep (typeRep px) "" <> " x" <> show sz

-- most of these functions zero at whole numbers and can short-circuit,
-- so a random offset should give a better signal of real world performance
generate2DCoords
  :: ( UniformRange a
     , RealFrac a
     , StatefulGen g IO
     )
  => g
  -> Int
  -> Int
  -> IO (a, a)
generate2DCoords g i j = do
  offsetX <- uniformRM (0.00001, 0.99999) g
  offsetY <- uniformRM (0.00001, 0.99999) g
  let r = fromIntegral i
      c = fromIntegral j

  pure (r + offsetX, c + offsetY)
{-# INLINE generate2DCoords #-}

createEnv2 :: forall a. (U.Unbox a, UniformRange a, RealFrac a) => Int -> IO (Seed, U.Vector (a, a))
createEnv2 sz = do
  g <- newAtomicGenM =<< newStdGen
  seed <- uniformRM (minBound, maxBound) g
  v <- U.generateM sz $ \i ->
    generate2DCoords
      g
      (i `div` (sz `div` 2))
      (i `mod` (sz `div` 2))
  pure (seed, v)
{-# INLINE createEnv2 #-}

benchMany2
  :: forall a
   . (Typeable a, UniformRange a, U.Unbox a, RealFrac a)
  => String
  -> Int
  -> Noise2 a
  -> Benchmark
benchMany2 lbl sz f =
  env (createEnv2 sz) $ \ ~(seed, v) ->
    bench (label lbl sz (Proxy @(U.Vector a))) $
      nf (U.map (uncurry (noise2At f seed))) v
{-# INLINE benchMany2 #-}

baseline2 :: Int -> [Benchmark]
baseline2 sz =
  [ bgroup
      "baseline2"
      [ benchMany2 @Float "" sz (const2 1)
      , benchMany2 @Double "" sz (const2 2)
      ]
  ]

benchPerlin2 :: Int -> Int -> [Benchmark]
benchPerlin2 octaves sz =
  [ bgroup
      "perlin2"
      [ benchMany2 @Float "" sz perlin2
      , benchMany2 @Double "" sz perlin2
      , benchMany2 @Float "fractal" sz (fractal2 defaultFractalConfig{octaves} perlin2)
      , benchMany2 @Double "fractal" sz (fractal2 defaultFractalConfig{octaves} perlin2)
      , benchMany2 @Float "ridged" sz (ridged2 defaultFractalConfig{octaves} perlin2)
      , benchMany2 @Double "ridged" sz (ridged2 defaultFractalConfig{octaves} perlin2)
      , benchMany2 @Float "billow" sz (billow2 defaultFractalConfig{octaves} perlin2)
      , benchMany2 @Double "billow" sz (billow2 defaultFractalConfig{octaves} perlin2)
      , benchMany2 @Float "pingPong" sz (pingPong2 defaultFractalConfig{octaves} defaultPingPongStrength perlin2)
      , benchMany2 @Double "pingPong" sz (pingPong2 defaultFractalConfig{octaves} defaultPingPongStrength perlin2)
      ]
  ]

benchOpenSimplex2 :: Int -> Int -> [Benchmark]
benchOpenSimplex2 octaves sz =
  [ bgroup
      "openSimplex2"
      [ benchMany2 @Float "" sz openSimplex2
      , benchMany2 @Double "" sz openSimplex2
      , benchMany2 @Float "fractal" sz (fractal2 defaultFractalConfig{octaves} openSimplex2)
      , benchMany2 @Double "fractal" sz (fractal2 defaultFractalConfig{octaves} openSimplex2)
      , benchMany2 @Float "ridged" sz (ridged2 defaultFractalConfig{octaves} openSimplex2)
      , benchMany2 @Double "ridged" sz (ridged2 defaultFractalConfig{octaves} openSimplex2)
      , benchMany2 @Float "billow" sz (billow2 defaultFractalConfig{octaves} openSimplex2)
      , benchMany2 @Double "billow" sz (billow2 defaultFractalConfig{octaves} openSimplex2)
      , benchMany2 @Float "pingPong" sz (pingPong2 defaultFractalConfig{octaves} defaultPingPongStrength openSimplex2)
      , benchMany2 @Double "pingPong" sz (pingPong2 defaultFractalConfig{octaves} defaultPingPongStrength openSimplex2)
      ]
  ]

benchOpenSimplexSmooth2 :: Int -> Int -> [Benchmark]
benchOpenSimplexSmooth2 octaves sz =
  [ bgroup
      "superSimplex2"
      [ benchMany2 @Float "" sz superSimplex2
      , benchMany2 @Double "" sz superSimplex2
      , benchMany2 @Float "fractal" sz (fractal2 defaultFractalConfig{octaves} superSimplex2)
      , benchMany2 @Double "fractal" sz (fractal2 defaultFractalConfig{octaves} superSimplex2)
      , benchMany2 @Float "ridged" sz (ridged2 defaultFractalConfig{octaves} superSimplex2)
      , benchMany2 @Double "ridged" sz (ridged2 defaultFractalConfig{octaves} superSimplex2)
      , benchMany2 @Float "billow" sz (billow2 defaultFractalConfig{octaves} superSimplex2)
      , benchMany2 @Double "billow" sz (billow2 defaultFractalConfig{octaves} superSimplex2)
      , benchMany2 @Float "pingPong" sz (pingPong2 defaultFractalConfig{octaves} defaultPingPongStrength superSimplex2)
      , benchMany2 @Double "pingPong" sz (pingPong2 defaultFractalConfig{octaves} defaultPingPongStrength superSimplex2)
      ]
  ]

benchValue2 :: Int -> Int -> [Benchmark]
benchValue2 octaves sz =
  [ bgroup
      "value2"
      [ benchMany2 @Float "" sz value2
      , benchMany2 @Double "" sz value2
      , benchMany2 @Float "fractal" sz (fractal2 defaultFractalConfig{octaves} value2)
      , benchMany2 @Double "fractal" sz (fractal2 defaultFractalConfig{octaves} value2)
      , benchMany2 @Float "ridged" sz (ridged2 defaultFractalConfig{octaves} value2)
      , benchMany2 @Double "ridged" sz (ridged2 defaultFractalConfig{octaves} value2)
      , benchMany2 @Float "billow" sz (billow2 defaultFractalConfig{octaves} value2)
      , benchMany2 @Double "billow" sz (billow2 defaultFractalConfig{octaves} value2)
      , benchMany2 @Float "pingPong" sz (pingPong2 defaultFractalConfig{octaves} defaultPingPongStrength value2)
      , benchMany2 @Double "pingPong" sz (pingPong2 defaultFractalConfig{octaves} defaultPingPongStrength value2)
      ]
  ]

benchValueCubic2 :: Int -> Int -> [Benchmark]
benchValueCubic2 octaves sz =
  [ bgroup
      "valueCubic2"
      [ benchMany2 @Float "" sz valueCubic2
      , benchMany2 @Double "" sz valueCubic2
      , benchMany2 @Float "fractal" sz (fractal2 defaultFractalConfig{octaves} valueCubic2)
      , benchMany2 @Double "fractal" sz (fractal2 defaultFractalConfig{octaves} valueCubic2)
      , benchMany2 @Float "ridged" sz (ridged2 defaultFractalConfig{octaves} valueCubic2)
      , benchMany2 @Double "ridged" sz (ridged2 defaultFractalConfig{octaves} valueCubic2)
      , benchMany2 @Float "billow" sz (billow2 defaultFractalConfig{octaves} valueCubic2)
      , benchMany2 @Double "billow" sz (billow2 defaultFractalConfig{octaves} valueCubic2)
      , benchMany2 @Float "pingPong" sz (pingPong2 defaultFractalConfig{octaves} defaultPingPongStrength valueCubic2)
      , benchMany2 @Double "pingPong" sz (pingPong2 defaultFractalConfig{octaves} defaultPingPongStrength valueCubic2)
      ]
  ]

benchCombo2 :: Int -> Int -> [Benchmark]
benchCombo2 octaves sz =
  [ bgroup
      "numeric combination"
      [ benchMany2 @Float "perlin * opensimplex/2" sz $
          openSimplex2 / 2 * perlin2
      , benchMany2 @Float "(4 * perlin) / 4" sz $
          4 * perlin2 / 4
      , benchMany2 @Float "(perlin + perlin + perlin + perlin) / 4" sz $
          (perlin2 + perlin2 + perlin2 + perlin2) / 4
      , benchMany2 @Float "fractal (perlin * opensimplex/2)" sz $
          fractal2 defaultFractalConfig{octaves} (openSimplex2 / 2 * perlin2)
      , benchMany2 @Float "fractal perlin * fractal opensimplex" sz $
          fractal2 defaultFractalConfig{octaves} perlin2
            * fractal2 defaultFractalConfig{octaves} openSimplex2
      ]
  ]

benchCellular2 :: Int -> Int -> [Benchmark]
benchCellular2 _ sz =
  [ bgroup
      "cellular2"
      [ benchMany2 @Float
          "DistEuclidean CellValue"
          sz
          (cellular2 defaultCellularConfig{cellularDistanceFn = DistEuclidean, cellularResult = CellValue})
      , benchMany2 @Float
          "DistEuclidean Distance2Add"
          sz
          (cellular2 defaultCellularConfig{cellularDistanceFn = DistEuclidean, cellularResult = Distance2Add})
      , benchMany2 @Float
          "DistManhattan CellValue"
          sz
          (cellular2 defaultCellularConfig{cellularDistanceFn = DistManhattan, cellularResult = CellValue})
      , benchMany2 @Float
          "DistManhattan Distance2Add"
          sz
          (cellular2 defaultCellularConfig{cellularDistanceFn = DistManhattan, cellularResult = Distance2Add})
      , benchMany2 @Double
          "DistEuclidean CellValue"
          sz
          (cellular2 defaultCellularConfig{cellularDistanceFn = DistEuclidean, cellularResult = CellValue})
      , benchMany2 @Double
          "DistEuclidean Distance2Add"
          sz
          (cellular2 defaultCellularConfig{cellularDistanceFn = DistEuclidean, cellularResult = Distance2Add})
      , benchMany2 @Double
          "DistManhattan CellValue"
          sz
          (cellular2 defaultCellularConfig{cellularDistanceFn = DistManhattan, cellularResult = CellValue})
      , benchMany2 @Double
          "DistManhattan Distance2Add"
          sz
          (cellular2 defaultCellularConfig{cellularDistanceFn = DistManhattan, cellularResult = Distance2Add})
      ]
  ]

-- offsets are drawn per element (like createEnv2) and the strides are
-- independent per axis; the previous version drew one offset triple and
-- collapsed the index math to ~4 distinct points
createEnv3 :: (U.Unbox a, UniformRange a, RealFrac a) => Int -> IO (Seed, U.Vector (a, a, a))
createEnv3 sz = do
  g <- newAtomicGenM =<< newStdGen
  seed <- uniformRM (minBound, maxBound) g
  let d = ceiling (fromIntegral sz ** (1 / 3) :: Double)
  !ixs <- U.generateM sz $ \i -> do
    offsetX <- uniformRM (0.00001, 0.99999) g
    offsetY <- uniformRM (0.00001, 0.99999) g
    offsetZ <- uniformRM (0.00001, 0.99999) g
    let !x = fromIntegral (i `mod` d)
        !y = fromIntegral ((i `div` d) `mod` d)
        !z = fromIntegral (i `div` (d * d))
    pure (x + offsetX, y + offsetY, z + offsetZ)
  pure (seed, ixs)
{-# INLINE createEnv3 #-}

benchMany3
  :: forall a
   . (Typeable a, UniformRange a, RealFrac a, U.Unbox a)
  => String
  -> Int
  -> Noise3 a
  -> Benchmark
benchMany3 lbl sz f =
  env (createEnv3 sz) $ \ ~(seed, v) ->
    bench (label lbl sz (Proxy @(U.Vector a))) $
      nf (U.map (\(x, y, z) -> noise3At f seed x y z)) v
{-# INLINE benchMany3 #-}

baseline3 :: Int -> [Benchmark]
baseline3 sz =
  [ bgroup
      "baseline3"
      [ benchMany3 @Float "" sz (const3 1)
      , benchMany3 @Double "" sz (const3 2)
      ]
  ]

benchPerlin3 :: Int -> Int -> [Benchmark]
benchPerlin3 octaves sz =
  [ bgroup
      "perlin3"
      [ benchMany3 @Float "" sz perlin3
      , benchMany3 @Double "" sz perlin3
      , benchMany3 @Float "fractal" sz (fractal3 defaultFractalConfig{octaves} perlin3)
      , benchMany3 @Double "fractal" sz (fractal3 defaultFractalConfig{octaves} perlin3)
      , benchMany3 @Float "ridged" sz (ridged3 defaultFractalConfig{octaves} perlin3)
      , benchMany3 @Double "ridged" sz (ridged3 defaultFractalConfig{octaves} perlin3)
      , benchMany3 @Float "billow" sz (billow3 defaultFractalConfig{octaves} perlin3)
      , benchMany3 @Double "billow" sz (billow3 defaultFractalConfig{octaves} perlin3)
      , benchMany3 @Float "pingPong" sz (pingPong3 defaultFractalConfig{octaves} defaultPingPongStrength perlin3)
      , benchMany3 @Double "pingPong" sz (pingPong3 defaultFractalConfig{octaves} defaultPingPongStrength perlin3)
      ]
  ]

benchOpenSimplex3 :: Int -> Int -> [Benchmark]
benchOpenSimplex3 octaves sz =
  [ bgroup
      "openSimplex3"
      [ benchMany3 @Float "" sz openSimplex3
      , benchMany3 @Double "" sz openSimplex3
      , benchMany3 @Float "fractal" sz (fractal3 defaultFractalConfig{octaves} openSimplex3)
      , benchMany3 @Double "fractal" sz (fractal3 defaultFractalConfig{octaves} openSimplex3)
      ]
  ]

benchSuperSimplex3 :: Int -> Int -> [Benchmark]
benchSuperSimplex3 octaves sz =
  [ bgroup
      "superSimplex3"
      [ benchMany3 @Float "" sz superSimplex3
      , benchMany3 @Double "" sz superSimplex3
      , benchMany3 @Float "fractal" sz (fractal3 defaultFractalConfig{octaves} superSimplex3)
      , benchMany3 @Double "fractal" sz (fractal3 defaultFractalConfig{octaves} superSimplex3)
      ]
  ]

benchCellular3 :: Int -> [Benchmark]
benchCellular3 sz =
  [ bgroup
      "cellular3"
      [ benchMany3 @Float
          "DistEuclidean CellValue"
          sz
          (cellular3 defaultCellularConfig{cellularDistanceFn = DistEuclidean, cellularResult = CellValue})
      , benchMany3 @Float
          "DistEuclidean Distance2Add"
          sz
          (cellular3 defaultCellularConfig{cellularDistanceFn = DistEuclidean, cellularResult = Distance2Add})
      , benchMany3 @Double
          "DistEuclidean CellValue"
          sz
          (cellular3 defaultCellularConfig{cellularDistanceFn = DistEuclidean, cellularResult = CellValue})
      , benchMany3 @Double
          "DistEuclidean Distance2Add"
          sz
          (cellular3 defaultCellularConfig{cellularDistanceFn = DistEuclidean, cellularResult = Distance2Add})
      ]
  ]

benchValue3 :: Int -> Int -> [Benchmark]
benchValue3 octaves sz =
  [ bgroup
      "value3"
      [ benchMany3 @Float "" sz value3
      , benchMany3 @Double "" sz value3
      , benchMany3 @Float "fractal" sz (fractal3 defaultFractalConfig{octaves} value3)
      , benchMany3 @Double "fractal" sz (fractal3 defaultFractalConfig{octaves} value3)
      , benchMany3 @Float "ridged" sz (ridged3 defaultFractalConfig{octaves} value3)
      , benchMany3 @Double "ridged" sz (ridged3 defaultFractalConfig{octaves} value3)
      , benchMany3 @Float "billow" sz (billow3 defaultFractalConfig{octaves} value3)
      , benchMany3 @Double "billow" sz (billow3 defaultFractalConfig{octaves} value3)
      , benchMany3 @Float "pingPong" sz (pingPong3 defaultFractalConfig{octaves} defaultPingPongStrength value3)
      , benchMany3 @Double "pingPong" sz (pingPong3 defaultFractalConfig{octaves} defaultPingPongStrength value3)
      ]
  ]

benchValueCubic3 :: Int -> Int -> [Benchmark]
benchValueCubic3 octaves sz =
  [ bgroup
      "valueCubic3"
      [ benchMany3 @Float "" sz valueCubic3
      , benchMany3 @Double "" sz valueCubic3
      , benchMany3 @Float "fractal" sz (fractal3 defaultFractalConfig{octaves} valueCubic3)
      , benchMany3 @Double "fractal" sz (fractal3 defaultFractalConfig{octaves} valueCubic3)
      , benchMany3 @Float "ridged" sz (ridged3 defaultFractalConfig{octaves} valueCubic3)
      , benchMany3 @Double "ridged" sz (ridged3 defaultFractalConfig{octaves} valueCubic3)
      , benchMany3 @Float "billow" sz (billow3 defaultFractalConfig{octaves} valueCubic3)
      , benchMany3 @Double "billow" sz (billow3 defaultFractalConfig{octaves} valueCubic3)
      , benchMany3 @Float "pingPong" sz (pingPong3 defaultFractalConfig{octaves} defaultPingPongStrength valueCubic3)
      , benchMany3 @Double "pingPong" sz (pingPong3 defaultFractalConfig{octaves} defaultPingPongStrength valueCubic3)
      ]
  ]

benchMassiv2
  :: forall a
   . (Typeable a, U.Unbox a, RealFrac a, UniformRange a)
  => String
  -> Int
  -> Int
  -> Noise2 a
  -> Benchmark
benchMassiv2 lbl !w !h noiseF =
  env
    ( do
        g <- newAtomicGenM =<< newStdGen
        seed <- uniformRM (minBound, maxBound) g
        -- This intentionally breaks the optimizer's ability to DCE. If we try to calculate
        -- inline it seems that GHC is smart enough to figure out that the whole-number
        -- integral points involve relatively simple code paths.
        (arr :: MA.Array MA.U MA.Ix2 (a, a)) <-
          MA.generateArray @MA.U MA.Par (MA.Sz2 h w) $ \(i MA.:. j) ->
            generate2DCoords g i j

        pure (seed, arr)
    )
    $ \ ~(seed, arr) ->
      bench (label lbl (w * h) (Proxy @a)) $
        nf
          ( MA.computeP @MA.U
              . MA.map
                ( \(!x, !y) ->
                    noise2At noiseF seed x y
                )
          )
          arr
{-# INLINE benchMassiv2 #-}

benchMassivBase2 :: Int -> Int -> [Benchmark]
benchMassivBase2 !w !h =
  [ bgroup
      "massiv base2"
      [ benchMassiv2 @Float "perlin2" w h perlin2
      , benchMassiv2 @Double "perlin2" w h perlin2
      , benchMassiv2 @Float "openSimplex2" w h openSimplex2
      , benchMassiv2 @Double "openSimplex2" w h openSimplex2
      , benchMassiv2 @Float "superSimplex2" w h superSimplex2
      , benchMassiv2 @Double "superSimplex2" w h superSimplex2
      , benchMassiv2 @Float "value2" w h value2
      , benchMassiv2 @Double "value2" w h value2
      , benchMassiv2 @Float "valueCubic2" w h valueCubic2
      , benchMassiv2 @Double "valueCubic2" w h valueCubic2
      , benchMassiv2 @Float
          "cellular2"
          w
          h
          (cellular2 defaultCellularConfig{cellularDistanceFn = DistEuclidean, cellularResult = CellValue})
      , benchMassiv2 @Double
          "cellular2"
          w
          h
          (cellular2 defaultCellularConfig{cellularDistanceFn = DistEuclidean, cellularResult = CellValue})
      ]
  ]

benchMassivFractal2 :: Int -> Int -> Int -> [Benchmark]
benchMassivFractal2 octaves w h =
  [ bgroup
      "massiv fractal2"
      [ benchMassiv2 @Float "perlin2 fractal" w h (fractal2 defaultFractalConfig{octaves} perlin2)
      , benchMassiv2 @Double "perlin2 fractal" w h (fractal2 defaultFractalConfig{octaves} perlin2)
      , benchMassiv2 @Float "value2 fractal" w h (fractal2 defaultFractalConfig{octaves} value2)
      , benchMassiv2 @Double "value2 fractal" w h (fractal2 defaultFractalConfig{octaves} value2)
      , benchMassiv2 @Float "openSimplex2 fractal" w h (fractal2 defaultFractalConfig{octaves} openSimplex2)
      , benchMassiv2 @Double "openSimplex2 fractal" w h (fractal2 defaultFractalConfig{octaves} openSimplex2)
      , benchMassiv2 @Float "superSimplex2 fractal" w h (fractal2 defaultFractalConfig{octaves} superSimplex2)
      , benchMassiv2 @Double "superSimplex2 fractal" w h (fractal2 defaultFractalConfig{octaves} superSimplex2)
      ]
  ]