【发布时间】:2016-06-02 17:29:58
【问题描述】:
下面的函数是找到一个数字n其中1^3 + 2^3 + ... + (n-1) ^3 + n^3 = m。这个功能有没有可能针对速度进行优化?
findNb :: Integer -> Integer
findNb m = findNb' 1 0
where findNb' n m' =
if m' == m then n - 1
else if m' < m then findNb' (n + 1) (m' + n^3)
else -1
我知道使用数学公式可以更快地解决问题。
我问的原因是 JavaScript / C# 中的类似实现似乎比 Haskell 中快得多。我只是好奇它是否可以优化。谢谢。
EDIT1:添加更多关于朗姆酒时间的证据
Haskell 版本:
与main = print (findNb2 152000000000000000000000):
-
使用 -O2 编译并进行分析:
ghc -o testo2.exe -O2 -prof -fprof-auto -rtsopts pileofcube.hs。以下是分析报告的总时间:总时间 = 0.19 秒(190 毫秒)(190 滴答声 @ 1000 us,1 个处理器)
-
使用 -O2 编译但不进行分析:
ghc -o testo22.exe -O2 pileofcube.hs。在 powershell 中使用Measure-Command {./testo22.exe}运行它。结果是:毫秒:157
JavaScript 版本:
代码:
function findNb(m) {
let n = 0;
let sum = 0;
while (sum < m) {
n++;
sum += Math.pow(n, 3);
}
return sum === m ? n : -1;
}
var d1 = new Date();
findNb(152000000000000000000000);
console.log(new Date() - d1);
结果:45 毫秒在同一台机器上的 Chrome 中运行
EDIT2:添加 C# 版本
正如@Berji 和@Bakuriu 评论的那样,与上面的JavaScript 版本相比是不公平的,因为它使用了底层的双精度浮点数,甚至无法给出正确的答案。所以我用 C# 实现了它,下面是代码和结果:
static void Main(string[] args)
{
BigInteger m = BigInteger.Parse("152000000000000000000000");
var s = new Stopwatch();
s.Start();
long n = 0;
BigInteger sum = 0;
while (sum < m)
{
n++;
sum += BigInteger.Pow(n, 3);
}
Console.WriteLine(sum == m ? n : -1);
s.Stop();
Console.WriteLine($"Escaped Time: {s.ElapsedMilliseconds} milliseconds.");
}
结果:转义时间:457 毫秒。
结论
Haskell 版本比 C# 快...
我一开始就错了,因为我的 JavaScript 知识很差,我没有意识到 JavaScript 在后台使用双精度浮点数。
在这一点上,这个问题似乎不再有意义了......
【问题讨论】:
-
你编译了你的程序,你能提供一些证据证明你的主张吗?通常你使用
seq或bang 模式来使计算更加严格。切换到 Into 或 Word64 也提供了显着的加速 -
@epsilonhalbe 添加了更多详细信息。我认为 Word64 不起作用,因为输入可能超出范围。
-
请注意,在 JavaScript 中所有数字都是双精度浮点数。您的
m已经超出整数可精确表示的范围;这段代码既不会给出准确的结果,也不能与 Haskell 的Integers 相比。 -
等效的 Haskell 代码的类型为
Double -> Double,并使用** 3而不是^3。 -
@Bakuriu,
^3不会比**3快吗?
标签: performance haskell