【问题标题】:Optimize haskell function with huge numbers of power function call使用大量的幂函数调用优化 haskell 函数
【发布时间】: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)

  1. 使用 -O2 编译并进行分析:ghc -o testo2.exe -O2 -prof -fprof-auto -rtsopts pileofcube.hs。以下是分析报告的总时间:

    总时间 = 0.19 秒(190 毫秒)(190 滴答声 @ 1000 us,1 个处理器)

  2. 使用 -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 -&gt; Double,并使用 ** 3 而不是 ^3
  • @Bakuriu,^3 不会比**3 快吗?

标签: performance haskell


【解决方案1】:

Haskell 也可以使用 Double 在更短的时间内得到错误答案:

% time ./so
./so  0.03s user 0.00s system 95% cpu 0.038 total

而且 Javascript 也可以通过 npm-installing big-integer 并在任何地方使用 bigInt 而不是 Double 来获得正确的结果:

% node so.js
^C
node so.js  35.62s user 0.30s system 93% cpu 38.259 total

...或者它可能没有那么微不足道。

【讨论】:

    【解决方案2】:

    编辑:后来我意识到这不是问题作者想要的。我会把它留在那里以防有人想知道有问题的公式,否则请忽略。


    确实有一个公式可以让您在恒定时间内(而不是 n 次迭代)进行计算。由于我不记得学校的确切公式,所以我做了一些搜索,这里是:https://proofwiki.org/wiki/Sum_of_Sequence_of_Cubes

    在 haskell 代码中,这将转换为

    findNb n = n ^ 2 * (n + 1) ^ 2 / 4
    

    我认为应该更快。

    【讨论】:

    • 是的,但他的意思是他不想改变算法,他想使用幼稚的方法作为基准,找出所讨论的语言和编译器的性能问题。跨度>
    • 哦,确实,我误读了这个问题(这会教你不要在早餐前这样做)。感谢您指出。
    【解决方案3】:

    不确定该算法的这种措辞是否更快,但试试这个?

    findNb :: Integer -> Integer
    findNb m = length $ takeWhile (<=m) $ scanl1 (+) [n^3 | n <- [1..]]
    

    (不过,这在未定义的情况下具有不同的语义。)

    【讨论】:

      猜你喜欢
      • 2011-08-30
      • 2018-05-08
      • 2021-02-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多