Haskell与C++中的简单π(x)
Simple π(x) in Haskell vs C++
我正在学习Haskell。我的兴趣是把它用于个人计算机实验。现在,我正试着看看哈斯克尔能跑多快。许多人声称与C(++)等同,如果这是真的,我会非常高兴(我应该注意,无论它是否快,我都会使用Haskell,但快仍然是一件好事)。
我的测试程序用一个非常简单的算法实现了π(x):素数加1。素数在1和√x之间没有整数除数。这不是一场算法之战,这纯粹是为了编译器性能。
Haskell在我的电脑上似乎慢了6倍,这很好(仍然比纯Python快100倍),但这可能只是因为我是Haskell的新手。
现在,我的问题是:在不更改算法的情况下,如何优化Haskell实现?Haskell的性能真的与C相当吗
这是我的Haskell
代码:
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])
这是我的C++
代码:
#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版本正在prime
中构建一个惰性列表,只是为了测试它是否为null。这似乎确实是一个瓶颈。以下版本的运行速度与我机器上的C++版本一样快:
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
用-O2编译时为3.31s,而用gcc 4.8编译C++时为3.18s,当n=5000000时为-O3。
当然,"猜测"程序优化缓慢的地方并不是一个很好的方法。幸运的是,Haskell有很好的评测工具。
使用编译和运行
$ ghc --make primes.hs -O2 -prof -auto-all -fforce-recomp && ./primes 5000000 +RTS -p
给出
# 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 ---
这清楚地表明CCD_ 4是事物变热的地方。有关评测的更多信息,我将向您介绍真实世界Haskell,第25章。
要真正了解发生了什么,您可以查看GHC的中间语言之一Core,它将向您展示优化后的代码外观。哈斯克尔维基上有一些好的信息。除非必要,否则我不建议这样做,但很高兴知道这种可能性是存在的。
对于您的其他问题:
1) 在不更改算法的情况下,我如何优化Haskell实现?
评测,并尝试编写内部循环,这样它们就不会进行任何内存分配,并且可以由编译器严格控制。这样做需要一些实践和经验。
2) Haskell的性能真的与C相当吗?
这取决于情况。GHC是惊人的,经常可以很好地优化你的程序。如果你知道你在做什么,你通常可以接近优化C的性能(C速度的100%-200%)。也就是说,这些优化并不总是简单或美观的,高级Haskell可能会更慢。但不要忘记,当使用Haskell时,您会获得惊人的表现力和高级抽象。对于除性能最关键的应用程序外的所有应用程序,它通常都足够快,即使这样,您也可以通过一些评测和性能优化来非常接近C。
我不认为Haskell版本(最初的版本,经过第一个答案的改进)与C++版本是等效的。原因是:两者都只考虑第二个元素(在素数函数中),而C++版本扫描每个元素(在isPrime()函数中只有i++)。
当我修复这个问题时(在C++的isPrime()函数中将i++更改为i+=2),我的运行时间几乎是优化Haskell版本的1/3(2.1s C++vs 6s Haskell)。
两者的输出保持不变(当然)。请注意,这并不是对C++版本的具体操作,只是对Haskell版本中已经应用的技巧的改编。
- 在c++中用vector填充一个简单的动态数组
- (C++)分析树以计算返回错误值的简单算术表达式
- 我的简单if-else语句是如何无法访问的代码
- 使用简单类型列表实现的指数编译时间.为什么
- 如何在BST的这个简单递归实现中消除警告
- 一种在C++中读取TXT配置文件的简单方法
- 关于简单C++函数(is_palindrome)的逻辑的问题
- 显示错误输出的简单数组排序程序
- 当无法使用模板和宏时,生成类型变体C++代码的最简单方法是什么?
- 退出简单while循环时出现问题
- 为什么简单的算术减法在"if"条件下不起作用?
- C++-字符串是否包含一个带有简单循环的单词
- 关于 c++ 函数中指针赋值的简单问题
- 从函数返回任意简单类型的数据
- 如何在没有函数的情况下编写此代码并使C++更简单?
- 有没有办法简单地从 GPU 调用多个 cpp 输出文件?
- 在简单示例中,Python3 + ctypes 回调会导致内存泄漏
- 当简单捕获中的标识符显示为参数的声明符 ID 时,没有编译器诊断
- 如何使用 samtools C API 构建一个简单的主.cpp文件
- CPU 瓶颈;处理具有许多非静态对象的 3D 场景渲染的简单方法