【问题标题】:Haskell - efficient equivalent of for loop?Haskell - 等效于 for 循环?
【发布时间】:2011-08-21 00:39:36
【问题描述】:

我一直在做一些实验,这是我发现的。考虑以下 C 程序:

int main()
{
   for(int i = 0;
       i < 1000000;
       ++i)
   {}
}

以及下面的 Haskell 程序:

import System.IO

loop :: Int -> IO ()
loop n = if 0 == n then return () else loop (n-1)

main = loop 1000000

这是上述 C 程序的time 的输出:

real    0m0.003s
user    0m0.000s
sys 0m0.000s

...对于 Haskell 程序:

real    0m0.028s
user    0m0.027s
sys 0m0.000s

一开始以为gcc检测到空循环并优化出来,但是增加迭代次数后,程序的运行时间也增加了。以下是两个程序的time 的输出,迭代次数设置为 10000000:

C 版

real    0m0.024s
user    0m0.023s
sys 0m0.000s

Haskell 版本

real    0m0.245s
user    0m0.247s
sys 0m0.000s

如您所见,Haskell 程序慢了 10 倍。

问题是:Haskell 中for 循环的有效替代方案是什么?正如我们刚刚看到的,简单递归会使程序减慢大约 10 倍(这可能与尾递归优化有关)。

【问题讨论】:

  • 出于好奇,优化后的c程序生成的汇编代码是否完全去掉了循环?
  • 噗,while 循环。呃。 :P
  • 这个明显的“基准”是一场闹剧,很抱歉。在循环中什么都不做是完全不存在的用例。函数式编程中for 循环的想法确实没有意义。迭代元素以执行计算的方法将根据计算的内容而有很大差异。选择一个真实的用例,你可能会得到一个更好、更现实的答案。
  • 谎言,该死的谎言和基准。
  • @Grigory Javadyan,假设您使用的是 GHC 编译器,如果您想对程序进行任何操作,您需要打开优化。例如。 ghc -O2 -fllvm 用于以绩效为导向的工作。如果您的目标是观看程序静止不动,请编写一个循环并运行它。

标签: c loops haskell recursion


【解决方案1】:

对于循环构造,您通常必须以 Worker/Wrapper 样式编写以帮助 GHC 发现优化,而不是在外部函数级别重复。

Grigory Javadyan 在评论中说原始版本在 -O3 时得到了优化,我希望这个版本在 -O2 时被检测到:

import System.IO

loop :: Int -> IO ()
loop n = go n
  where
    go n | n <= 0 = return ()
    go n          = go (n-1)

main = loop 1000000

【讨论】:

    【解决方案2】:

    首先,您需要将您的 C 代码翻译成这个,

    main = go 0
        where
            go :: Int -> IO ()
            go i | i < 1000000 = go (i+1)
                 | otherwise   = return ()
    

    哪个 ghc 针对空程序进行了优化。它将最终值移动到寄存器中,与它进行比较,然后返回()

    Main_zdwa_info: 
        cmpq    $1000000, %r14          # imm = 0xF4240
        movl    $1000000, %eax          # imm = 0xF4240
        cmovlq  %rax, %r14
        movq    (%rbp), %rax
        movl    $ghczmprim_GHCziUnit_Z0T_closure+1, %ebx
        jmpq    *%rax  # TAILCALL
    

    运行时:

    $ time ./A
    ./A  0.00s user 0.00s system 88% cpu 0.004 total
    

    不需要时间。


    不过,一般而言,GHC 会发出 equivalent loops to e.g. GCC,用于尾递归函数。特别是,您需要使用ghc -O2 -fllvm 编译您的数字基准以获得最佳结果。如果您不想优化您的程序,那么 ghc 会很乐意准确地执行您指定的程序,在这种情况下,这会涉及大量冗余工作,这些工作将在更高的优化级别上被删除。

    有关分析 Haskell 程序低级性能的更多信息,请查看RWH ch25.

    【讨论】:

    • 这看起来像是糟糕的代码。有多余的动作。我的意思是,要么完全消除循环,要么保留它。不要保存它的 3 条指令。
    • main 还是要返回一个 IO()。
    猜你喜欢
    • 2018-08-06
    • 2014-01-12
    • 1970-01-01
    • 1970-01-01
    • 2019-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多