jvm-0.5.0: benchmarks/Main.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
import Control.DeepSeq (NFData(..))
import Criterion.Main as Criterion
import Control.Monad (replicateM, replicateM_, void)
import Data.Int
import Data.IORef
import Data.Singletons (SomeSing(..))
import Data.Text (Text)
import qualified Foreign.Concurrent as Concurrent
import qualified Foreign.ForeignPtr as ForeignPtr
import Foreign.JNI
import Foreign.Marshal.Alloc (mallocBytes, finalizerFree, free)
import Foreign.Marshal.Array (callocArray, mallocArray)
import Foreign.Ptr (castPtr)
import Language.Java
newtype Box a = Box { unBox :: a }
-- Not much sense in deepseq'ing foreign pointers. But needed for call to 'env'
-- below.
instance NFData (Box a) where rnf (Box a) = seq a ()
jabs :: Int32 -> IO Int32
jabs x = callStatic "java.lang.Math" "abs" x
jniAbs :: JClass -> JMethodID -> Int32 -> IO Int32
jniAbs klass method x = callStaticIntMethod klass method [coerce x]
intValue :: Int32 -> IO Int32
intValue x = do
jx <- reflect x
call jx "intValue"
compareTo :: Int32 -> Int32 -> IO Int32
compareTo x y = do
jx <- reflect x
jy <- reflect y
call jx "compareTo" jy
incrHaskell :: Int32 -> IO Int32
incrHaskell x = return (x + 1)
foreign import ccall unsafe getpid :: IO Int
benchCalls :: Benchmark
benchCalls =
env ini $ \ ~(Box klass, method) ->
bgroup "Calls"
[ bgroup "Java calls"
[ bench "static method call: unboxed single arg / unboxed return" $ nfIO $ jabs 1
, bench "static method call: boxed single arg / boxed return" $
perBatchEnvWithCleanup
(\batchSize -> do
pushLocalFrame (2 * fromIntegral batchSize)
Box <$> reflect ("123" :: Text)
)
(\_ _ -> void (popLocalFrame jnull)) $
\(Box jStringInteger) -> do
_ <- callStatic "java.lang.Integer" "valueOf" jStringInteger
:: IO (J ('Class "java.lang.Integer"))
return ()
, bench "jni static method call: unboxed single arg / unboxed return" $ nfIO $ jniAbs klass method 1
, bench "method call: no args / unboxed return" $ nfIO $ intValue 1
, bench "method call: boxed single arg / unboxed return" $ nfIO $ compareTo 1 1
, bench "getClass" $
perBatchEnvWithCleanup
(pushLocalFrame . (2*) . fromIntegral)
(\_ _ -> void (popLocalFrame jnull)) $
\() -> do
_ <- getClass (SClass "java/lang/Math")
return ()
, bench "getStaticMethodID" $
perBatchEnvWithCleanup
(\_ -> Box <$> getClass (SClass "java/lang/Math"))
(\_ (Box c) -> deleteLocalRef c) $
\ ~(Box c) -> do
_ <- getStaticMethodID c "abs" absSignature
return ()
, bench "getMethodID" $
perBatchEnvWithCleanup
(\_ -> Box <$> getClass (SClass "java/lang/Integer"))
(\_ (Box c) -> deleteLocalRef c) $
\ ~(Box c) -> do
_ <- getMethodID c "intValue" (methodSignature [] (SPrim "int"))
return ()
]
, bgroup "Haskell calls"
[ bench "incr haskell" $ nfIO $ incrHaskell 1
, bench "ffi haskell" $ nfIO $ getpid
]
]
where
absSignature = methodSignature [SomeSing (sing :: Sing ('Prim "int"))] (SPrim "int")
ini = do
klass <- findClass (referenceTypeName (SClass "java/lang/Math"))
method <- getStaticMethodID klass "abs" absSignature
return (Box klass, method)
benchRefs :: Benchmark
benchRefs =
env (Box <$> new) $ \ ~(Box (jobj :: JObject)) ->
bgroup "References"
[ bench "local reference" $ nfIO $ do
_ <- newLocalRef jobj
return ()
, bench "global reference" $ nfIO $ do
_ <- newGlobalRef jobj
return ()
, bench "global reference (no finalizer)" $ nfIO $ do
_ <- newGlobalRefNonFinalized jobj
return ()
, bench "Foreign.Concurrent.newForeignPtr" $ nfIO $ do
_ <- Concurrent.newForeignPtr (unsafeObjectToPtr jobj) (return ())
return ()
, bench "Foreign.ForeignPtr.newForeignPtr" $ nfIO $ do
-- Approximate cost of malloc: 50ns. Ideally would move out of benchmark
-- but finalizer not idempotent.
ptr <- mallocBytes 4
_ <- ForeignPtr.newForeignPtr finalizerFree ptr
return ()
, bench "local frame / 1 reference" $ nfIO $ do
pushLocalFrame 30
_ <- newLocalRef jobj
_ <- popLocalFrame jnull
return ()
, bench "delete 1 local ref" $ nfIO $
newLocalRef jobj >>= deleteLocalRef
, bench "local frame / 30 references" $ nfIO $ do
pushLocalFrame 30
replicateM_ 30 $ newLocalRef jobj
_ <- popLocalFrame jnull
return ()
, bench "delete 30 local refs" $ nfIO $
replicateM_ 30 $ newLocalRef jobj >>= deleteLocalRef
]
benchNew :: Benchmark
benchNew =
bgroup "new"
[ bench "Integer" $
perBatchEnvWithCleanup
(pushLocalFrame . (2*) . fromIntegral)
(\_ _ -> void (popLocalFrame jnull)) $
\() -> do
_ <- new (2 :: Int32) :: IO (J ('Class "java.lang.Integer"))
return ()
, bench "Integer.valueOf" $
perBatchEnvWithCleanup
(pushLocalFrame . (2*) . fromIntegral)
(\_ _ -> void (popLocalFrame jnull)) $
\() -> do
_ <- callStatic "java.lang.Integer" "valueOf" (2 :: Int32)
:: IO (J ('Class "java.lang.Integer"))
return ()
, envWithCleanup allocTextPtr freeTextPtr $ \ ~(Box (ptr, len)) ->
bench "newString" $
perBatchEnvWithCleanup
(pushLocalFrame . (2*) . fromIntegral)
(\_ _ -> void (popLocalFrame jnull)) $
\() ->
void $ newString ptr (fromIntegral len)
, envWithCleanup allocTextPtr freeTextPtr $ \ ~(Box (_ptr, len)) ->
bench "newArray" $
perBatchEnvWithCleanup
(pushLocalFrame . (2*) . fromIntegral)
(\_ _ -> void (popLocalFrame jnull)) $
\() ->
void $ newByteArray (fromIntegral len)
, envWithCleanup allocTextPtr freeTextPtr $ \ ~(Box (ptr, len)) ->
bench "newDirectByteBuffer" $
perBatchEnvWithCleanup
(pushLocalFrame . (2*) . fromIntegral)
(\_ _ -> void (popLocalFrame jnull)) $
\() ->
void $ newDirectByteBuffer (castPtr ptr) (2 * len)
]
where
allocTextPtr = do
let len = 128
dst <- callocArray len
return $ Box (dst, fromIntegral len)
freeTextPtr (Box (p, _)) = free p
benchArrays :: Benchmark
benchArrays =
bgroup "Arrays" $ (++ otherBenchmarks) $ (`map` [128, 256, 512]) $ \arraySize ->
let n :: Num b => b
n = fromIntegral (arraySize :: Int) in
env (Box <$> mallocArray n) $ \ ~(Box bytes) ->
env (Box <$> newArray n) $ \ ~(Box jbytes) ->
bgroup (show (n :: Int))
[ bench "getByteArrayElements" $
perBatchEnvWithCleanup (\_ -> newIORef []) (const cleanArrays) $
\ref -> do
p <- getByteArrayElements jbytes
modifyIORef ref ((jbytes, p) :)
, bench "releaseByteArrayElements" $
perBatchEnv
(\batchSize ->
replicateM (fromIntegral batchSize) (getByteArrayElements jbytes)
>>= newIORef
) $
\ref -> do
arrays <- readIORef ref
case arrays of
x : xs -> do
releaseByteArrayElements jbytes x
writeIORef ref xs
_ -> error "not enough arrays"
, bench "getByteArrayRegion" $ nfIO $
getByteArrayRegion jbytes 0 n bytes
, bench "setByteArrayRegion" $ nfIO $
setByteArrayRegion jbytes 0 n bytes
]
where
otherBenchmarks =
[ bench "getObjectArrayElement" $
perBatchEnvWithCleanup
(\batchSize -> do
pushLocalFrame (2 * fromIntegral batchSize)
Box <$> newArray 100
)
(\_ _ -> void (popLocalFrame jnull)) $
\(Box jObjectArray) -> do
_ <- getObjectArrayElement (jObjectArray :: JObjectArray) 40 :: IO JObject
return ()
]
cleanArrays ref =
readIORef ref >>= mapM_ (uncurry releaseByteArrayElements)
benchDirectBuffers :: Benchmark
benchDirectBuffers =
bgroup "DirectBuffers" $ (`map` [128, 256, 512]) $ \bufferSize ->
let n :: Num b => b
n = fromIntegral (bufferSize :: Int) in
env (Box <$> mallocArray n) $ \ ~(Box bytes) ->
bgroup (show bufferSize)
[ bench "getDirectBufferAddress" $
perBatchEnvWithCleanup
(\_ -> Box <$> newDirectByteBuffer bytes n)
(\_ -> deleteLocalRef . unBox) $
void . getDirectBufferAddress . unBox
, bench "getDirectBufferCapacity" $
perBatchEnvWithCleanup
(\_ -> Box <$> newDirectByteBuffer bytes n)
(\_ -> deleteLocalRef . unBox) $
void . getDirectBufferCapacity . unBox
]
main :: IO ()
main = withJVM [] $ do
Criterion.defaultMain
[ benchCalls
, benchRefs
, benchNew
, benchArrays
, benchDirectBuffers
]