Simple π(x) in Haskell vs C++
我在学哈斯克尔。我的兴趣是把它用于个人计算机实验。现在,我想看看哈斯克尔能有多快。许多人声称与C(++)持平,如果这是真的,我会非常高兴(我应该注意,我将使用haskell,不管它是否快速,但快速仍然是一件好事)。
我的测试程序用一个非常简单的算法实现π(x):素数加1的结果。素数在1和√x之间没有整数除数。这不是算法之争,这纯粹是为了编译器的性能。
在我的电脑上,haskell似乎慢了6倍,这很好(仍然比纯python快100倍),但那可能只是因为我是haskell的新手。
现在,我的问题是:在不改变算法的情况下,如何优化haskell实现?haskell真的在与c进行性能对等吗?
这是我的
1 2 3 4 5 6 7 8 9 10 11 12 13 | import System.Environment -- a simple integer square root isqrt :: Int -> Int isqrt = floor . sqrt . fromIntegral -- primality test prime :: Int -> Bool prime x = null [x | q <- [3, 5..isqrt x], rem x q == 0] main = do n <- fmap (read . head) getArgs print $ length $ filter prime (2:[3, 5..n]) |
这是我的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #include <iostream> #include <cmath> #include <cstdlib> using namespace std; bool isPrime(int); int main(int argc, char* argv[]) { int primes = 10000, count = 0; if (argc > 1) { primes = atoi(argv[1]); } if (isPrime(2)) { count++; } for (int i = 3; i <= primes; i+=2) { if (isPrime(i)){ count++; } } cout << count << endl; return 0; } bool isPrime(int x){ for (int i = 2; i <= floor(sqrt(x)); i++) { if (x % i == 0) { return false; } } return true; } |
号
您的haskell版本正在
1 2 3 4 5 | prime :: Int -> Bool prime x = go 3 where go q | q <= isqrt x = if rem x q == 0 then False else go (q+2) go _ = True |
3.31秒,当用-O2与3.18s编译时,用GCC 4.8和-O3为n=5000000。
当然,"猜测"程序优化的速度慢并不是一个很好的方法。幸运的是,Haskell在板上有很好的分析工具。
编译和运行
1 | $ ghc --make primes.hs -O2 -prof -auto-all -fforce-recomp && ./primes 5000000 +RTS -p |
号
给予
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | # primes.prof Thu Feb 20 00:49 2014 Time and Allocation Profiling Report (Final) primes +RTS -p -RTS 5000000 total time = 5.71 secs (5710 ticks @ 1000 us, 1 processor) total alloc = 259,580,976 bytes (excludes profiling overheads) COST CENTRE MODULE %time %alloc prime.go Main 96.4 0.0 main Main 2.0 84.6 isqrt Main 0.9 15.4 individual inherited COST CENTRE MODULE no. entries %time %alloc %time %alloc MAIN MAIN 45 0 0.0 0.0 100.0 100.0 main Main 91 0 2.0 84.6 100.0 100.0 prime Main 92 2500000 0.7 0.0 98.0 15.4 prime.go Main 93 326103491 96.4 0.0 97.3 15.4 isqrt Main 95 0 0.9 15.4 0.9 15.4 --- >8 --- |
这清楚地表明,
为了真正理解正在发生的事情,您可以查看(其中一个)GHC的中间语言核心,它将向您展示优化后代码的外观。哈斯克尔维基上有一些很好的信息。除非必要,否则我不建议这样做,但最好知道这种可能性存在。
其他问题:
1)如何在不改变算法的情况下优化haskell实现?
配置文件,并尝试编写内部循环,这样它们就不会进行任何内存分配,并且编译器可以严格执行这些操作。这样做需要一些实践和经验。
2)Haskell是否真的与C进行性能对等?
视情况而定。GHC是令人惊奇的,而且经常可以很好地优化您的程序。如果你知道你在做什么,你通常可以接近优化C的性能(100%-200%的C的速度)。这就是说,这些优化并不总是简单或好看的眼睛和高水平哈斯克尔可以更慢。但别忘了,在使用haskell时,你会获得惊人的表现力和高层次的抽象。对于除性能最关键的应用程序以外的所有应用程序,它通常都足够快,即使这样,通过一些分析和性能优化,您也可以非常接近C。
我不认为Haskell版本(原始和改进的第一个答案)等同于C++版本。原因是:两者都只考虑每一个元素(在质数函数中),而C++版本扫描每个元素(仅在IsPrime()函数中的I+++。
当我修复这个(在C++的iSimple()函数中改变i++到i+=2时,我得到了优化的Haskell版本(2.1S C++VS 6S Haskell)的运行时间的几乎1/3。
当然,两者的输出是相同的。请注意,这不是C++版本的特定opim化,只是适应于Haskell版本中已经应用的技巧。