【发布时间】:2015-03-08 17:36:09
【问题描述】:
我有以下一组函数来计算 Haskell 中小于或等于数字 n 的素数的数量。
算法接受一个数字,检查它是否能被 2 整除,然后检查它是否能被奇数整除,直到被检查数字的平方根。
-- is a numner, n, prime?
isPrime :: Int -> Bool
isPrime n = n > 1 &&
foldr (\d r -> d * d > n || (n `rem` d /= 0 && r))
True divisors
-- list of divisors for which to test primality
divisors :: [Int]
divisors = 2:[3,5..]
-- pi(n) - the prime counting function, the number of prime numbers <= n
primesNo :: Int -> Int
primesNo 2 = 1
primesNo n
| isPrime n = 1 + primesNo (n-1)
| otherwise = 0 + primesNo (n-1)
main = print $ primesNo (2^22)
使用带有 -O2 优化标志的 GHC,在我的系统上计算 n = 2^22 的素数需要大约 3.8 秒。以下 C 代码大约需要 0.8 秒:
#include <stdio.h>
#include <math.h>
/*
compile with: gcc -std=c11 -lm -O2 c_primes.c -o c_orig
*/
int isPrime(int n) {
if (n < 2)
return 0;
else if (n == 2)
return 1;
else if (n % 2 == 0)
return 0;
int uL = sqrt(n);
int i = 3;
while (i <= uL) {
if (n % i == 0)
return 0;
i+=2;
}
return 1;
}
int main() {
int noPrimes = 0, limit = 4194304;
for (int n = 0; n <= limit; n++) {
if (isPrime(n))
noPrimes++;
}
printf("Number of primes in the interval [0,%d]: %d\n", limit, noPrimes);
return 0;
}
这个算法在 Java 中大约需要 0.9 秒,在 JavaScript(在 Node 上)中大约需要 1.8 秒,所以感觉 Haskell 版本比我预期的要慢。无论如何,我可以在不改变算法的情况下更有效地在 Haskell 中编码吗?
编辑
@dfeuer 提供的以下版本的 isPrime 将运行时间缩短了一秒,将其缩短至 2.8 秒(从 3.8 降低)。虽然这仍然比 JavaScript (Node) 慢,如这里所示,需要大约 1.8 秒,Yet Another Language Speed Test。
isPrime :: Int -> Bool
isPrime n
| n <= 2 = n == 2
| otherwise = odd n && go 3
where
go factor
| factor * factor > n = True
| otherwise = n `rem` factor /= 0 && go (factor+2)
编辑
在上面的isPrime函数中,函数go调用factor * factor为单个除数n.我想将 factor 与 n 的平方根进行比较会更有效,因为每个 n 只需计算一次.但是,使用下面的代码,计算时间增加了大约 10%,是否每次计算不等式时都会重新计算 n 的平方根(对于每个 因子 )?
isPrime :: Int -> Bool
isPrime n
| n <= 2 = n == 2
| otherwise = odd n && go 3
where
go factor
| factor > upperLim = True
| otherwise = n `rem` factor /= 0 && go (factor+2)
where
upperLim = (floor.sqrt.fromIntegral) n
【问题讨论】:
-
有一些方法可以改进您的 Haskell 代码。不过,您能否在不更改算法 的情况下准确说明您的意思?
-
当然,“不改变算法”是指底层算法,而不是它的实现方式。例如,通过更改算法,如果仅针对素数而不是所有奇数检查数字的可分性,则代码会更有效。换句话说,我仍然希望代码检查所有奇数,直到被检查数字的平方根。我想我问的是“在 C 中使用 for 循环或 while 循环会更快吗”。
-
关于
isPrime和sqrt的问题,uppareLim绝对不会针对每个因素重新计算。将定义提升一级是一项基本优化。事实上,对我来说,在不使用 LLVM 时使用sqrt会更快(与它差不多)。不知道您为什么会变慢,也许可以尝试进行更多基准测试? -
我不确定发生了什么,我刚刚用
factor > upperLim对版本进行了十次计时,平均运行时间为 3.01 秒。带有factor * factor > n的版本运行十次,平均为 2.62 秒。我正在使用 GHC 7.8.4 并且仅使用 -O2 标志进行编译,我不使用 LLVM。
标签: performance haskell primes