Haskell 哈斯克尔三角微基准

Haskell 哈斯克尔三角微基准,haskell,ghc,Haskell,Ghc,考虑一下这个简单的“基准”: 汇编: Haskell版本编译为:ghc-O2 triangle.hs(ghc 7.4.1) C版本编译为:gcc-O2-o triangle-C triangle.C(gcc 4.6.3) 运行时间: 哈斯克尔:4.308s雷亚尔 C:1.145s雷亚尔 即使是这样一个简单的、可能优化得很好的程序,Haskell的速度也慢了近4倍,这样的行为还可以吗?哈斯克尔在哪里浪费时间?我不知道你的“板凳”有多重要。 我同意列表理解语法使用起来“很好”,但是如果你想比

考虑一下这个简单的“基准”:

汇编:

  • Haskell版本编译为:
    ghc-O2 triangle.hs
    (ghc 7.4.1)
  • C版本编译为:
    gcc-O2-o triangle-C triangle.C
    (gcc 4.6.3)
运行时间:

  • 哈斯克尔:4.308s雷亚尔
  • C:1.145s雷亚尔

即使是这样一个简单的、可能优化得很好的程序,Haskell的速度也慢了近4倍,这样的行为还可以吗?哈斯克尔在哪里浪费时间?

我不知道你的“板凳”有多重要。 我同意列表理解语法使用起来“很好”,但是如果你想比较这两种语言的性能,你应该在一个更公平的测试中比较它们。 我的意思是,创建一个可能有很多元素的列表,然后计算它的长度,这和增加(三重循环)中的计数器完全不同

因此,也许haskell有一些很好的优化,可以检测到您正在做什么,并且从不创建列表,但我不会依赖于此编写代码,您可能也不应该这样做


如果您需要快速计数,我认为您不会这样编写程序,那么为什么要在这个工作台上这么做呢?

Haskell版本是在浪费时间分配装箱整数和元组

您可以通过运行带有标志
+RTS-s
的haskell程序来验证这一点。对我来说,输出的统计数据包括:

  80,371,600 bytes allocated in the heap
C版本的直接编码速度更快,因为编译器可以使用非固定整数并跳过元组分配:

n :: Int
n = 1000
main = do
  print $ f n

f :: Int -> Int
f max = go 0 1 1 1
  where go cnt a b c
          | a > max = cnt
          | b > max = go cnt (a+1) 1 1
          | c > max = go cnt a (b+1) 1
          | a^2+b^2==c^2 = go (cnt+1) a b (c+1)
          | otherwise = go cnt a b (c+1)
见:

此版本的运行时间是
1.920s
而C版本的运行时间是
1.212s

Haskell可以很好地优化-但是您需要适当的技术,并且需要知道自己在做什么

这个列表理解语法很优雅,但很浪费。您应该阅读,以了解有关您的评测机会的更多信息。在这种情况下,您创建了许多列表脊椎和装箱的
Int
s,根本没有任何理由。请看这里:

你绝对应该做点什么。编辑:@opqdonut刚刚发布了一个很好的答案,说明了如何加快这一过程


请记住,下次在比较任何基准测试之前,先评测您的应用程序。Haskell使编写惯用代码变得简单,但也隐藏了许多复杂性

你能用4个元素的数组abc[4]来尝试同样的C代码吗?其中abc[0]是a,abc[1]是b,abc[2]是c,最后一个是虚拟的矢量化如果你做更多的(微观)基准测试,我真的可以推荐使用Criteria-请参阅以了解更多细节。问题是,Haskell肯定不会创建列表,因为内存占用仍然很低。“那么我想知道,它在哪里浪费时间呢?”马丁,你看过它产生的核心吗?GHC很可能会为中间列表生成惰性代码,在这种情况下,我很惊讶它的运行时间不到C代码的4倍。@Martin它肯定会生成三元组列表,但
length
会在生成时使用它,所以,内存中的列表单元格从来没有超过几个。仍然惊讶于GHC的效率。因此,我们得到的代码比C对应的代码更慢、更详细。测量C型haskell有什么意义?它没有给出关于haskell对于一个开发者有多有用的答案。如果你喜欢在所有语言中都像C一样编程,那么你应该只使用C。“真正的程序员可以用任何语言编写Fortran程序”
  80,371,600 bytes allocated in the heap
n :: Int
n = 1000
main = do
  print $ f n

f :: Int -> Int
f max = go 0 1 1 1
  where go cnt a b c
          | a > max = cnt
          | b > max = go cnt (a+1) 1 1
          | c > max = go cnt a (b+1) 1
          | a^2+b^2==c^2 = go (cnt+1) a b (c+1)
          | otherwise = go cnt a b (c+1)
  51,728 bytes allocated in the heap