最近我试图确定使用矢量存储类型计算波形所需的时间。

我想这样做而不需要打印长度或类似的东西。最后我想出了以下两个定义。这看起来很简单,据我所知,它在我第一次运行该函数时按预期打印了一个非零的计算时间,但我想知道这里是否有任何我遗漏的懒惰警告。

import System.IO
import System.CPUTime
import qualified Data.Vector.Storable as V

timerIO f = do
  start <- getCPUTime
  x <- f
  let !y = x
  end <- getCPUTime
  let diff = (fromIntegral (end - start)) / (10^12)
  print $ "Computation time: " ++ show diff ++ " sec\n"

timer f = timerIO $ do return f

main :: IO ()
main = do
  let sr = 1000.0
      time = V.map (/ sr) $ V.enumFromN 0 120000 :: V.Vector Float
      wave = V.map (\x -> sin $ x * 2 * pi * 10) time :: V.Vector Float

  timer wave
  timer wave

打印,
Computation time: 0.16001 sec
Computation time: 0.0 sec

这里有什么隐藏的错误吗?我真的不确定带有严格标志的 let 真的是最好的方法。有没有更简洁的方法来写这个?是否有任何我应该知道的标准功能已经做到了这一点?

编辑:我应该提到我已经阅读了关于标准的内容,但在这种情况下,我并不是在寻找一种可靠的方法来计算仅用于分析目的的平均时间;相反,我正在寻找一种简单/低开销的方法来将单个计时器集成到我的程序中,以便在应用程序正常运行期间跟踪某些计算的时间。标准很酷,但这是一个略有不同的用例。

最佳答案

如果评估为弱头部范式就足够了 - 对于严格的 Vector s 或 UArray s 它是 - 那么你的时间代码运行良好¹,但是,你可以在 monadic 绑定(bind)上放一个 bang 来代替 let 绑定(bind)中的 bang 模式,

start <- getCPUTime
!x <- f
end <- getCPUTime

对我来说哪个更好看,或者你可以使用 Control.Exception.evaluate
start <- getCPUTime
evaluate f
end <- getCPUTime

它具有(假定的)便携性的优势,而 bang 模式是 GHC 的扩展。如果 WHNF 还不够,则需要强制进行全面评估,例如使用 rnfdeepseq ,例如
start <- getCPUTime
!x <- rnf `fmap` f
end <- getCPUTime

然而,重复地对相同的计算进行计时是很麻烦的。如果像你的例子一样,你给这个东西一个名字,然后叫它
timer wave
timer wave

编译器共享计算,所以它只执行一次,除了第一个 timer 调用返回零(或非常接近零)次。如果你用代码而不是名字来调用它,
timer (V.map (\x -> sin $ x * 2 * pi * 10) time :: V.Vector Float)
timer (V.map (\x -> sin $ x * 2 * pi * 10) time :: V.Vector Float)

如果编译器执行公共(public)子表达式消除,它仍然可以共享计算。虽然 GHC 没有做太多 CSE,但它做了一些,我相当有信心它会发现并分享这一点(在编译时进行优化)。为了可靠地使编译器重复计算,您需要隐藏它们相同的事实(或使用一些低级内部结构),这在不影响计算所需时间的情况下是不容易做到的。

¹ 如果计算需要大量时间,则效果很好。如果只需要很短的时间,外部影响(CPU 负载、调度等)引入的抖动会使单个计时变得过于不可靠。然后您应该进行多次测量,为此,正如在别处提到的,criterion 库是减轻您编写健壮时序代码负担的绝佳方式。

关于haskell - 这个 Haskell 函数在严格计时计算方面有什么问题吗?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/8772055/

10-12 12:55
查看更多