如何可靠地比较Haskell和C的运行时间?

15

我使用Criterion库编写了我的Haskell函数的基准测试。现在我正在用C实现相同的算法,以与Haskell进行性能比较。问题是如何可靠地做到这一点?Criterion会执行很多花哨的操作,如考虑时钟调用开销和对结果进行统计分析。我猜想,如果我只测量我的C函数所需的时间,它将无法与Criterion返回的结果进行比较。在他的Criterion原始帖子中,Bryan O'Sullivan写道:“甚至可以使用criterion来对C代码和命令行程序进行基准测试。”问题是如何做到?Takayuki Muranushi通过生成线程并调用可执行文件比较DFT的C实现与Haskell,但我担心这会增加大量附加开销(创建新线程,运行应用程序,输出到stdio,然后从中读取),使结果不可比较。我还考虑使用FFI,但同样担心额外的开销会使这种比较不公平。

如果没有办法使用Criterion来可靠地对C进行基准测试,那么您推荐哪些方法来进行C基准测试?我在这里阅读了一些问题,似乎有许多不同的函数可以测量系统时间,但它们要么提供以毫秒为单位的时间,要么具有大量的调用开销。

1
我并不认为在这里使用FFI是不合适的。我认为这是开销最小的方法。如果你将C导入标记为“unsafe”,那么它只会进行编组和简单的内联“call”指令。 - Mikhail Glushenkov
3
此外,您可以通过对一个什么也不做的 C 函数进行基准测试来估计 FFI/exec 开销,并查看它添加了多少噪音。 - Mikhail Glushenkov
其实我不太了解FFI的工作原理,所以也许我高估了开销,但是复制数据到和从外部函数中呢? - Jan Stolarek
1
这取决于你正在处理什么类型的数据。例如,如果它是一个 ByteString,你可以使用 unsafeUseAsCString。或者在测量之前进行编组。 - Mikhail Glushenkov
1个回答

13

FFI可以以不增加太多开销的方式使用。考虑以下程序(完整代码可在这里找到):

foreign import ccall unsafe "mean" c_mean :: Ptr CInt -> CUInt -> IO CFloat

main :: IO ()
main = do
  buf <- mallocBytes (bufSize * sizeOfCInt)
  fillBuffer buf 0
  m <- c_mean buf (fromIntegral bufSize)
  print $ realToFrac m

这个C调用被编译成以下的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,));

这里是汇编代码:

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

因此,如果您将C导入标记为unsafe并在测量之前进行所有封送处理,那么您的C调用基本上只是一个内联call指令 - 就像您在C中执行所有基准测试一样。 当我对一个什么也不做的C函数进行基准测试时,这是Criterion报告的内容:

benchmarking c_nothing
mean: 13.99036 ns, lb 13.65144 ns, ub 14.62640 ns, ci 0.950
std dev: 2.306218 ns, lb 1.406215 ns, ub 3.541156 ns, ci 0.950
found 10 outliers among 100 samples (10.0%)
  9 (9.0%) high severe
variance introduced by outliers: 91.513%
variance is severely inflated by outliers

这大约比我的机器预估的时钟分辨率小400倍(约为5.5微秒)。为了比较,这里是一个计算100个整数算术平均值函数的基准数据:

benchmarking c_mean
mean: 184.1270 ns, lb 183.5749 ns, ub 185.0947 ns, ci 0.950
std dev: 3.651747 ns, lb 2.430552 ns, ub 5.885120 ns, ci 0.950
found 6 outliers among 100 samples (6.0%)
  5 (5.0%) high severe
variance introduced by outliers: 12.329%
variance is moderately inflated by outliers

感谢您详细的回答。我不确定这是否适用于我,因为我返回一个双精度数组,并且必须将其转换为某个 Haskell 容器(可能是未打包的向量)。 - Jan Stolarek
1
Data.Vector.Storable 包含函数 unsafeToForeignPtr,它允许你从 C 代码中就地修改向量。 - Mikhail Glushenkov
@MikhailGlushenkov,该文档说明:“不能通过ForeignPtr修改数据。” - huon
@dbaupp 我应该链接到 Data.Vector.Storable.Mutable - Mikhail Glushenkov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接