这个Haskell函数是否存在严格计时的问题?

时间:2012-01-07 18:41:01

标签: haskell

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

我想这样做而不需要打印长度或类似的东西。最后我想出了以下两个定义。这看起来很简单,从我可以告诉它打印出非正常的计算时间,这是我第一次运行该函数,但我想知道是否有任何懒惰的警告,我错过了。

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是否真的是最好的方式。有没有更简洁的方式来写这个?是否有任何我已经知道的标准功能?

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

2 个答案:

答案 0 :(得分:4)

如果评估弱头正常形式就足够了 - 对于严格的VectorUArray来说是 - ,那么你的计时代码运作良好¹,但是,而不是让...绑定,你可以打击monadic绑定,

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

对我来说看起来更好,或者你可以使用Control.Exception.evaluate

start <- getCPUTime
evaluate f
end <- getCPUTime

具有(假设)可移植性的优点,而爆炸模式是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)

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

¹如果计算需要大量时间,那么效果很好。如果只需要很短的时间,外部影响(CPU负载,调度......)引入的抖动将使单个时序太不可靠。然后你应该进行多次测量,为此,正如其他地方所提到的,criterion库是一种很好的方法,可以减轻编写健壮时序代码的负担。

答案 1 :(得分:3)

您熟悉the deepseq package吗?它被the criterion package用于你所描述的目的。

说到这一点,你可能想要考虑criterion本身是否能满足你的需要。