How to reliably compare runtime of Haskell and C?
FFI can be used in such a way that it doesn't add much overhead. Consider the following program (full code available here):
foreign import ccall unsafe "mean" c_mean :: Ptr CInt -> CUInt -> IO CFloatmain :: IO ()main = do buf <- mallocBytes (bufSize * sizeOfCInt) fillBuffer buf 0 m <- c_mean buf (fromIntegral bufSize) print $ realToFrac m
The C call is compiled to the following Cmm:
s2ni_ret() { ... } c2qy: Hp = Hp + 12; if (Hp > I32[BaseReg + 92]) goto c2qC; _c2qD::I32 = I32[Sp + 4]; (_s2m3::F32,) = foreign "ccall" mean((_c2qD::I32, PtrHint), (100,));
Here's the assembly:
s2ni_info:.Lc2qy: addl $12,%edi cmpl 92(%ebx),%edi ja .Lc2qC movl 4(%ebp),%eax subl $4,%esp pushl $100 pushl %eax ffree %st(0) ;ffree %st(1) ;ffree %st(2) ;ffree %st(3) ffree %st(4) ;ffree %st(5) call mean
So, if you mark your C import as unsafe
and do all marshalling before measurement, your C call will be basically just an inline call
instruction - the same as if you were doing all benchmarking in C. Here's what Criterion reports when I benchmark a C function that does nothing:
benchmarking c_nothingmean: 13.99036 ns, lb 13.65144 ns, ub 14.62640 ns, ci 0.950std dev: 2.306218 ns, lb 1.406215 ns, ub 3.541156 ns, ci 0.950found 10 outliers among 100 samples (10.0%) 9 (9.0%) high severevariance introduced by outliers: 91.513%variance is severely inflated by outliers
This is approximately 400 times smaller than the estimated clock resolution on my machine (~ 5.5 us). For comparison, here's the benchmark data for a function that computes the arithmetic mean of 100 integers:
benchmarking c_meanmean: 184.1270 ns, lb 183.5749 ns, ub 185.0947 ns, ci 0.950std dev: 3.651747 ns, lb 2.430552 ns, ub 5.885120 ns, ci 0.950found 6 outliers among 100 samples (6.0%) 5 (5.0%) high severevariance introduced by outliers: 12.329%variance is moderately inflated by outliers