【问题标题】:Making Haskell Code faster使 Haskell 代码更快
【发布时间】:2015-02-17 21:25:16
【问题描述】:

有谁知道如何让这个 Haskell 代码更有趣?我在做Project Euler #14。 此代码运行时间为 4.029 秒:

collatz :: Int -> Int64 -> Int                                                                                                                                                 
collatz c 1 = c                                                                 
collatz c k                                                                     
    | even k    = collatz (c+1) (k `div` 2)                                     
    | otherwise = collatz (c+1) (3*k + 1)                                       

main = do                    
    print $ maximum (map (\i -> (collatz 1 i, i)) [1..1000000])

记忆 collat​​z 函数实际上增加了运行时间,所以我没有做任何记忆。 可比较的 C 代码运行时间为 0.239 秒:

int main(int argc, char *argv[])
{
    int maxlength = 0;
    int maxstart = 1;
    for (int i = 1; i <= 1000000; i++) {
        unsigned long k = i;
        int length = 1;
        while (k > 1) {
            length += 1;
            if (k % 2 == 0)
                k = k / 2;
            else
                k = 3*k + 1;
        }
        if (length > maxlength) {
            maxlength = length;
            maxstart = i;
        }
    }
    printf("%d, %d\n", maxlength, maxstart);
    return 0;
}

Haskell 代码用 ghc -O3 编译,C 代码用 gcc -std=c99 -O3 编译。

【问题讨论】:

  • 严格注释是第一件事,然后是shiftR x 1而不是div x 2(是的,仍然缺少一些关键优化)。这使您与 C 保持惊人的距离。
  • 这个特殊的 Project Euler 问题在过去引发了一大堆类似的性能问题。看看this search 以获得一堆建议。 This one 可能特别相关。
  • 你有 LLVM 吗?它在我的机器上用 -O2 -fllvm 运行 1.3 秒。
  • 是的,llvm 做了优化,使除法为右移,偶数测试为按位与。使用 -fllvm 编译代码,或者手动更改上述问题,我的代码现在运行时间为 0.420 秒。感谢大家的帮助。

标签: haskell


【解决方案1】:

这是来自 haskell wiki 的解决方案:

import Data.Array
import Data.List
import Data.Ord (comparing)

syrs n =
    a
    where
    a = listArray (1,n) $ 0:[1 + syr n x | x <- [2..n]]
    syr n x =
        if x' <= n then a ! x' else 1 + syr n x'
        where
        x' = if even x then x `div` 2 else 3 * x + 1

main =
    print $ maximumBy (comparing snd) $ assocs $ syrs 1000000

我的机器上的计算时间:

haskell|master⚡ ⇒ ghc -O2 prob14_memoize.hs
[1 of 1] Compiling Main             ( prob14_memoize.hs, prob14_memoize.o )
Linking prob14_memoize ...
haskell|master⚡ ⇒ time ./prob14_memoize
(837799,524)
./prob14_memoize  0.63s user 0.03s system 99% cpu 0.664 total

与原版相比:

haskell|master⚡ ⇒ ghc -O2 prob14_orig.hs
[1 of 1] Compiling Main             ( prob14_orig.hs, prob14_orig.o )
Linking prob14_orig ...
haskell|master⚡ ⇒ time ./prob14_orig
(525,837799)
./prob14_orig  2.77s user 0.01s system 99% cpu 2.777 total

【讨论】:

  • 是的,但是问题的规模足够小,以至于记忆化的开销实际上使它变慢了。此代码运行时间为 0.911 秒,而原始代码运行时间为 0.420 秒。两者都是用 ghc -O2 -fllvm 编译的
【解决方案2】:

我注意到这个问题主要是转发。见here

代码的主要问题是 ghc 默认不优化整数除法。 要手动修复我的代码,

collatz c k                                                                     
    | k .&. 1 == 0 = collatz (c+1) (k `shiftR` 1)                                     
    | otherwise    = collatz (c+1) (3*k + 1) 

但是,如果机器上安装了 LLVM,则可以使用以下代码编译原始代码

ghc -O2 -fllvm code.hs

LLVM 会进行必要的优化。这两种解决方案都使我的代码运行时间大约为 0.420 秒,这更接近于可比较的 C 代码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多