12

以下代码将类型中的多个类型截断Double为一个Word16(尽管我怀疑任何其他单词类型的行为相似,但我不得不为示例选择一个)。

truncate1 :: Double -> Word16
truncate1 = fromIntegral . (truncate :: Double -> Int)

如您所见,我首先将其截断为Int,然后才将其转换为Word16. 我将此函数与直接截断进行了基准测试:

truncate2 :: Double -> Word16
truncate2 = truncate

令我惊讶的是,第一个版本(首先通过Int类型)表现得更好。或者第二个更糟。根据标准输出:

benchmarking truncate/truncate1
mean: 25.42399 ns, lb -47.40484 ps, ub 67.87578 ns, ci 0.950
std dev: 145.5661 ns, lb 84.90195 ns, ub 244.2057 ns, ci 0.950
found 197 outliers among 100 samples (197.0%)
  97 (97.0%) low severe
  100 (100.0%) high severe
variance introduced by outliers: 99.000%
variance is severely inflated by outliers

benchmarking truncate/truncate2
mean: 781.0604 ns, lb 509.3264 ns, ub 1.086767 us, ci 0.950
std dev: 1.436660 us, lb 1.218997 us, ub 1.592479 us, ci 0.950
found 177 outliers among 100 samples (177.0%)
  77 (77.0%) low severe
  100 (100.0%) high severe
variance introduced by outliers: 98.995%
variance is severely inflated by outliers

老实说,我刚开始使用 Criterion,所以我不是使用它的专家,但我知道它25.42399 ns的执行时间比781.0604 ns. 我怀疑某些专业化在这里发挥了作用。是不是truncate2太慢了?既然如此,还能truncate改进吗?此外,有人知道更快的方法吗?我觉得对我不真正使用的类型进行了错误的转换。

提前致谢。

我正在使用 GHC-7.4.2 进行编译,启用了优化 ( -O2)。

4

1 回答 1

13

首先,请注意该模块GHC.Word 包含以下 RULE编译指示:

"truncate/Double->Word16"
    forall x. truncate (x :: Double) = (fromIntegral :: Int -> Word16) (truncate x)

这是一个简单的重写规则,可以精确地执行您truncate1提供的优化。所以我们有几个问题需要考虑:

为什么这是一个优化?

因为默认实现truncate是泛型的,支持任意Integral实例。您看到的速度差异是这种普遍性的成本;在将一种原始类型截断为另一种的特定情况下,有更快的方法可用。

因此,它似乎truncate1受益于一种专门的形式,而truncate2并非如此。

为什么truncate1更快?

GHC.FloatRealFrac定义实例的地方Double,我们有以下RULE编译指示:

"truncate/Double->Int"              truncate = double2Int

double2Int我们想要的优化形式在哪里。将此与RULE前面提到的进行比较 - 显然没有类似的原始操作专门用于转换DoubleWord16.

为什么不truncate2被重写呢?

引用 GHC 用户指南:

GHC 目前使用一种非常简单的语法匹配算法来匹配规则 LHS 和表达式。它寻求一种替换,使 LHS 和表达式在语法上等于模 alpha 转换。如果需要,模式(规则)而不是表达式会被 eta 扩展。

被匹配的表达式不是 eta 扩展的,也就是说匹配 on 的规则forall x. foo x将匹配 inbar y = foo y匹配in bar = foo

由于您的定义都是无点编写的,因此RULEforDouble -> Int匹配,但RULEforDouble -> Word16不匹配。

于 2013-02-11T22:20:24.853 回答