packages feed

croaring-ffi-1.0: bench/Bench/Bitmap64.hs

{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE NoFieldSelectors #-}

module Bench.Bitmap64 (benchmarks) where

import Bench.Values (Values (..), countWhere, dense, forEach, prefix, scattered64, withValues)
import Control.Monad (void)
import Data.Bitmap.Roaring.FFI.Bitmap qualified as Bitmap
import Data.Bitmap.Roaring.FFI.Bitmap64 (Bitmap64)
import Data.Bitmap.Roaring.FFI.Bitmap64 qualified as Bitmap64
import Data.Bitmap.Roaring.FFI.Bitmap64.BulkContext (BulkContext (..))
import Data.Bitmap.Roaring.FFI.Bitmap64.BulkContext qualified as BulkContext
import Data.Bitmap.Roaring.FFI.Bitmap64.Iterator qualified as Iterator
import Data.Word (Word64)
import Foreign.C.Types (CChar, CSize)
import Foreign.Marshal.Alloc (allocaBytesAligned, free, mallocBytes)
import Foreign.Marshal.Array (allocaArray, mallocArray)
import Foreign.Marshal.Utils (fillBytes)
import Foreign.Ptr (Ptr, freeHaskellFunPtr, nullPtr)
import Test.Tasty (withResource)
import Test.Tasty.Bench (Benchmark, bench, bgroup, whnfIO)

size :: Int
size = 1_000_000

benchmarks :: Benchmark
benchmarks =
  withValues (scattered64 1 size) $ \getScattered ->
    withValues (scattered64 2 size) $ \getOther ->
      withValues (dense size) $ \getDense ->
        withBitmap getScattered $ \getScatteredBitmap ->
          withBitmap getOther $ \getOtherBitmap ->
            withBitmap getDense $ \getDenseBitmap ->
              bgroup
                "Bitmap64"
                [ bgroup
                    "construction"
                    [ bgroup "scattered" $ construction getScattered
                    , bgroup "dense" $ construction getDense
                    ]
                , bgroup
                    "queries"
                    [ bgroup "hits" $ queries getScatteredBitmap getScattered
                    , bgroup "misses" $ queries getScatteredBitmap getOther
                    ]
                , bgroup
                    "algebra"
                    [ bgroup "scattered" $ algebra getScatteredBitmap getOtherBitmap
                    , bgroup "mixed" $ algebra getScatteredBitmap getDenseBitmap
                    ]
                , bgroup
                    "traversal"
                    [ bgroup "scattered" $ traversal getScatteredBitmap
                    , bgroup "dense" $ traversal getDenseBitmap
                    ]
                , bgroup
                    "serialization"
                    [ bgroup "scattered" $ serialization getScatteredBitmap
                    , bgroup "dense" $ serialization getDenseBitmap
                    ]
                ]

withBitmap :: IO (Values Word64) -> (IO Bitmap64 -> Benchmark) -> Benchmark
withBitmap getValues = withResource acquire Bitmap64.free
  where
    acquire = do
      values <- getValues
      Bitmap64.ofPtr (fromIntegral values.count) values.pointer

construction :: IO (Values Word64) -> [Benchmark]
construction getValues =
  [ bench "ofPtr" . whnfIO $ do
      values <- getValues
      Bitmap64.ofPtr (fromIntegral values.count) values.pointer >>= Bitmap64.free
  , filling "addMany" $ \bitmap values -> Bitmap64.addMany bitmap (fromIntegral values.count) values.pointer
  , filling "add" $ \bitmap values -> forEach values $ Bitmap64.add bitmap
  , filling "addChecked" $ \bitmap values -> forEach values $ void . Bitmap64.addChecked bitmap
  , filling "addBulk" $ \bitmap values ->
      allocaBytesAligned BulkContext.size BulkContext.alignment $ \memory -> do
        fillBytes memory 0 BulkContext.size
        forEach values . Bitmap64.addBulk bitmap $ BulkContext memory
  ]
  where
    filling :: String -> (Bitmap64 -> Values Word64 -> IO ()) -> Benchmark
    filling name fill =
      bench name . whnfIO $ do
        values <- getValues
        bitmap <- Bitmap64.create
        fill bitmap values
        Bitmap64.free bitmap

queries :: IO Bitmap64 -> IO (Values Word64) -> [Benchmark]
queries getBitmap getProbes =
  [ probing "contains" $ \bitmap probes -> countWhere probes $ Bitmap64.contains bitmap
  , probing "containsBulk" $ \bitmap probes ->
      allocaBytesAligned BulkContext.size BulkContext.alignment $ \memory -> do
        fillBytes memory 0 BulkContext.size
        countWhere probes . Bitmap64.containsBulk bitmap $ BulkContext memory
  , probing "rank of 1000" $ \bitmap probes -> countWhere (prefix 1000 probes) $ fmap (> 0) . Bitmap64.rank bitmap
  , bench "getCardinality" . whnfIO $ getBitmap >>= Bitmap64.getCardinality
  ]
  where
    probing name query =
      bench name . whnfIO $ do
        bitmap <- getBitmap
        probes <- getProbes
        query bitmap probes

algebra :: IO Bitmap64 -> IO Bitmap64 -> [Benchmark]
algebra getLeft getRight =
  [ fresh "and" Bitmap64.and
  , fresh "or" Bitmap64.or
  , fresh "xor" Bitmap64.xor
  , fresh "andnot" Bitmap64.andnot
  , measured "andCardinality" Bitmap64.andCardinality
  , measured "orCardinality" Bitmap64.orCardinality
  , measured "intersect" Bitmap64.intersect
  , measured "equals" Bitmap64.equals
  , bench "copy" . whnfIO $ getLeft >>= Bitmap64.copy >>= Bitmap64.free
  ]
  where
    fresh name operation = measured name $ \left right -> operation left right >>= Bitmap64.free
    measured name operation =
      bench name . whnfIO $ do
        left <- getLeft
        right <- getRight
        operation left right

traversal :: IO Bitmap64 -> [Benchmark]
traversal getBitmap =
  [ withResource (mallocArray size) free $ \getOutput ->
      bench "toUint64Array" . whnfIO $ do
        bitmap <- getBitmap
        getOutput >>= Bitmap64.toUint64Array bitmap
  , bench "Iterator.read" . whnfIO $ do
      bitmap <- getBitmap
      iterating bitmap $ \iterator ->
        allocaArray 256 $ \buffer -> do
          let loop !total = do
                count <- Iterator.read iterator buffer 256
                if count < 256 then pure $ total + count else loop $ total + count
          loop 0
  , bench "Iterator.advance" . whnfIO $ do
      bitmap <- getBitmap
      iterating bitmap $ \iterator -> do
        let loop !total = do
              present <- Iterator.hasValue iterator
              if present then do
                value <- Iterator.value iterator
                _ <- Iterator.advance iterator
                loop $ total + value
              else
                pure total
        loop 0
  , withResource (Bitmap.wrapCallback64 $ \_ _ -> pure True) freeHaskellFunPtr $ \getCallback ->
      bench "iterate" . whnfIO $ do
        bitmap <- getBitmap
        callback <- getCallback
        Bitmap64.iterate bitmap callback nullPtr
  ]
  where
    iterating bitmap continue = do
      iterator <- Iterator.create bitmap
      result <- continue iterator
      Iterator.free iterator
      pure result

data Buffer = Buffer
  { pointer :: Ptr CChar
  , size :: CSize
  }

withPortable :: IO Bitmap64 -> (IO Buffer -> Benchmark) -> Benchmark
withPortable getBitmap = withResource acquire release
  where
    acquire = do
      bitmap <- getBitmap
      measured <- Bitmap64.portableSizeInBytes bitmap
      pointer <- mallocBytes $ fromIntegral measured
      _ <- Bitmap64.portableSerialize bitmap pointer
      pure Buffer{pointer, size = measured}
    release buffer = free buffer.pointer

serialization :: IO Bitmap64 -> [Benchmark]
serialization getBitmap =
  [ bench "portableSizeInBytes" . whnfIO $ getBitmap >>= Bitmap64.portableSizeInBytes
  , bench "internalValidate" . whnfIO $ getBitmap >>= (`Bitmap64.internalValidate` nullPtr)
  , withPortable getBitmap $ \getBuffer ->
      bgroup
        "portable"
        [ bench "serialize" . whnfIO $ do
            bitmap <- getBitmap
            buffer <- getBuffer
            Bitmap64.portableSerialize bitmap buffer.pointer
        , bench "deserializeBounded" . whnfIO $ do
            buffer <- getBuffer
            Bitmap64.portableDeserializeBounded buffer.pointer buffer.size >>= Bitmap64.free
        , bench "deserializeFrozen" . whnfIO $ do
            buffer <- getBuffer
            Bitmap64.portableDeserializeFrozen buffer.pointer buffer.size >>= Bitmap64.free
        ]
  ]