自己定义严格的应用程序($!)并不会产生相同的性能

时间:2018-04-15 06:17:07

标签: haskell ghc

我最近在阅读this blog post by Michael Snoyman。在那里建议的练习中,我试图自己定义$!运算符:

import Prelude hiding ( ($!) )

($!) :: (a -> b) -> a -> b
($!) f x = x `seq` f x

mysum :: [Int] -> Int
mysum list0 =
  go list0 0
  where
    go [] total = total
    go (x:xs) total = go xs $! total + x

main = print $ mysum [1..1000000]

我认为这很好用,虽然记忆的使用很糟糕。我的第一个问题是这个。为什么这样做不好?

然后,我在Prelude中检查了它的定义。它写着:

($!)                    :: (a -> b) -> a -> b
f $! x                  = let !vx = x in f vx  -- see #2273

所以,我把它复制到我的代码中:

{-# LANGUAGE BangPatterns #-}

import Prelude hiding ( ($!) )

($!) :: (a -> b) -> a -> b
($!) f x =
  let !vx = x
   in f vx

mysum :: [Int] -> Int
mysum list0 =
  go list0 0
  where
    go [] total = total
    go (x:xs) total = go xs $! total + x

main = print $ mysum [1..1000000]

结果是:

Linking mysum4 ...
500000500000
     209,344,064 bytes allocated in the heap
     130,602,696 bytes copied during GC
      54,339,936 bytes maximum residency (8 sample(s))
          66,624 bytes maximum slop
              80 MB total memory in use (0 MB lost due to fragmentation)

您可以看到与使用Prelude $!运算符的结果相比有多糟糕:

Linking mysum4 ...
500000500000
     152,051,776 bytes allocated in the heap
          41,752 bytes copied during GC
          44,384 bytes maximum residency (2 sample(s))
          21,152 bytes maximum slop
               1 MB total memory in use (0 MB lost due to fragmentation)

我的第二个问题是这种差异来自哪里?

此外,我认为它可以像这样重写:

($!) :: (a -> b) -> a -> b
f $! !x = f x

有什么理由不这样做吗?这是我的第三个问题。

1 个答案:

答案 0 :(得分:26)

啊哈!这是一个优先问题。你忘了粘贴这行:

infixr 0  $!

所以当你使用自己的版本时,它会被解析为

go (x:xs) total = (go xs $! total) + x

这显然有可怕的表现。它几乎是巧合,它甚至可以给你正确的答案。