写最快的斐波那契


10

这是有关斐波那契数的又一挑战。

目标是尽快计算第20'000'000 斐波那契数。小数输出大约是4 MiB。它开始于:

28543982899108793710435526490684533031144309848579

输出的MD5总和为

fa831ff5dd57a830792d8ded4c24c2cb

您必须提交一个程序,该程序在运行时计算数量并将结果放入stdout。以我自己的机器衡量,最快的程序胜出。

以下是一些其他规则:

  • 您必须提交源代码和可在x64 Linux上运行的二进制文件
  • 源代码必须短于1 MiB,如果只有二进制代码那么小,则在汇编的情况下也可以接受。
  • 即使以变相的方式,也不得在二进制文件中包含要计算的数字。该数字必须在运行时计算。
  • 我的计算机有两个核心。您被允许使用并行

我从Internet上进行了一个小型实施,运行时间约为4.5秒。假设您有一个好的算法,那么克服这一挑战应该不会很困难。


1
杜德(Dude),像Sage这样具有不确定的浮点精度的东西,都可以在不到1/10秒的时间内运行。这只是一个简单的表达phi = (1+sqrt(5))/2
JBernardo

4
我们可以用十六进制输出数字吗?
基思·兰德尔

2
@基思·诺普(Keith Nope)。这是规范的一部分。
FUZxxl

3
由于将在您的 CPU 上对其进行测量,因此我们可能还会有更多有关它的信息,不是吗?英特尔还是AMD?L1和指令缓存的大小?指令集扩展?
JB

2
根据我的计算,您的起始字符串和MD5表示第20'000'000个数字,而不是第2'000'000个数字。
JB

Answers:


4

具有GMP的C,3.6秒

天哪,但GMP使代码变得丑陋。借助Karatsuba风格的技巧,我每加倍步数就减少了2次。现在,我正在阅读FUZxxl的解决方案,我不是第一个有此想法的人。我还有很多技巧...也许以后再尝试。

#include <gmp.h>
#include <stdio.h>

#define DBL mpz_mul_2exp(u,a,1);mpz_mul_2exp(v,b,1);mpz_add(u,u,b);mpz_sub(v,a,v);mpz_mul(b,u,b);mpz_mul(a,v,a);mpz_add(a,b,a);
#define ADD mpz_add(a,a,b);mpz_swap(a,b);

int main(){
    mpz_t a,b,u,v;
    mpz_init(a);mpz_set_ui(a,0);
    mpz_init(b);mpz_set_ui(b,1);
    mpz_init(u);
    mpz_init(v);

    DBL
    DBL
    DBL ADD
    DBL ADD
    DBL
    DBL
    DBL
    DBL ADD
    DBL
    DBL
    DBL ADD
    DBL
    DBL ADD
    DBL ADD
    DBL
    DBL ADD
    DBL
    DBL
    DBL
    DBL
    DBL
    DBL
    DBL
    DBL /*Comment this line out for F(10M)*/

    mpz_out_str(stdout,10,b);
    printf("\n");
}

内置gcc -O3 m.c -o m -lgmp


大声笑。除了标识符命名之外,这正是我的解决方案:)
JB

@JB:第一!:D
2011年

保持它;)我的下一个诀窍将受益于Haskell而不是
JB

首先欺骗我的袖子碰到了一个GHC错误。德拉特 我将不得不回到第二个,实现起来并不那么有趣,因此需要时间和动力。
JB

在我的机器上需要3.6秒。
FUZxxl

11

智者

嗯,您似乎认为最快的将是编译程序。没有二进制文件适合您!

print fibonacci(2000000)

在我的机器上,它需要0.10 cpu秒,0.15墙秒。

编辑:定时在控制台上,而不是笔记本上


1
我的想法不是要知道您的CAS可以执行此操作的速度,而是您可以自己编码的速度。
FUZxxl

11
作为记录,我只是把它说成是个聪明人。您没有说不使用内置函数。
2011年

5

哈斯克尔

尽管我自己没有写算法,但这是我自己的尝试。我宁愿从haskell.org复制它,并使其Data.Vector与著名的流融合一起使用:

import Data.Vector as V
import Data.Bits

main :: IO ()
main = print $ fib 20000000

fib :: Int -> Integer
fib n = snd . V.foldl' fib' (1,0) . V.dropWhile not $ V.map (testBit n) $ V.enumFromStepN (s-1) (-1) s
    where
        s = bitSize n
        fib' (f,g) p
            | p         = (f*(f+2*g),ss)
            | otherwise = (ss,g*(2*f-g))
            where ss = f*f+g*g

使用GHC 7.0.3和以下标志进行编译时,大约需要4.5秒:

ghc -O3 -fllvm fib.hs

很奇怪...我需要将20000000更改为40000000,才能打印期望的数字。
JB

知道了 应该enumFromStepN (s-1)代替enumFromStepN s
JB

@JB抱歉让您感到困惑。最初,我使用不同的值对程序进行了测试,以得到一个合理的数字,并将输出保存到不同的文件中。但是有些让我感到困惑的。我已经更新了数字以匹配期望的结果。
FUZxxl

@boothby不,我没有更改所需的斐波那契数,而是更改了参考输出,这是错误的。
FUZxxl 2011年

旁注:在我的机器上大约是1.5秒,但是LLVM和Data.Vector似乎都没有带来任何明显的优势。
JB

4

 MoO moO MoO mOo MOO OOM MMM moO moO
 MMM mOo mOo moO MMM mOo MMM moO moO
 MOO MOo mOo MoO moO moo mOo mOo moo

!(花一点时间。喝点牛奶...)


1
注意:尽管这确实有效,但它可能永远不会达到20,000,000 ...
Timtech

2

Mathematica,解释为:

First@Timing[Fibonacci[2 10^6]]

定时:

0.032 secs on my poor man's laptop.

当然,也没有二进制文件。


不会列印到stdout
2011年

@boothby错误。如果使用命令行界面,它将写入标准输出。例如见stackoverflow.com/questions/6542537/...
博士贝利萨留

不,我使用的是6.0版命令行界面。即使使用-batchoutput,它也只打印计时信息,而不打印斐波那契数。
2011年

抱歉,因为我没有mathematica,所以无法复制。
FUZxxl 2011年

5
curl 'http://www.wolframalpha.com/input/?i=Fibonacci%5B2+10^6%5D' | grep 'Decimal approximation:' | sed ... 它会根据您的Internet连接速度恒定地运行。;-)
ESultanik 2011年

2

Ocaml,我的笔记本电脑上为0.856秒

需要zarith库。我使用了Big_int,但与zarith相比,它的速度很慢。相同的代码花了10分钟!大部分时间都花在打印该死的数字上(大约9½分钟)!

module M = Map.Make
  (struct
    type t = int
    let compare = compare
   end)

let double b = Z.shift_left b 1
let ( +. ) b1 b2 = Z.add b1 b2
let ( *. ) b1 b2 = Z.mul b1 b2

let cache = ref M.empty 
let rec fib_log n =
  if n = 0
  then Z.zero
  else if n = 1
  then Z.one
  else if n mod 2 = 0
  then
    let f_n_half = fib_log_cached (n/2)
    and f_n_half_minus_one = fib_log_cached (n/2-1)
    in f_n_half *. (f_n_half +. double f_n_half_minus_one)
  else
    let f_n_half = fib_log_cached (n/2)
    and f_n_half_plus_one = fib_log_cached (n/2+1)
    in (f_n_half *. f_n_half) +.
    (f_n_half_plus_one *. f_n_half_plus_one)
and fib_log_cached n =
    try M.find n !cache
    with Not_found ->
      let res = fib_log n
      in cache := M.add n res !cache;
      res

let () =
  let res = fib_log 20_000_000 in
  Z.print res; print_newline ()

我简直不敢相信图书馆有多大的改变!


1
为了进行比较,@ boothby的解决方案需要0.875s才能在我的笔记本电脑上运行。看来区别可忽略不计。另外,显然我的笔记本电脑速度很快:o
ReyCharles 2012年

1

哈斯克尔

在我的系统上,这几乎与FUZxxl的回答一样快(〜18秒而不是〜17秒)。

main = print $ fst $ fib2 20000000

-- | fib2: Compute (fib n, fib (n+1)).
--
-- Having two adjacent Fibonacci numbers lets us
-- traverse up or down the series efficiently.
fib2 :: Int -> (Integer, Integer)

-- Guard against negative n.
fib2 n | n < 0 = error "fib2: negative index"

-- Start with a few base cases.
fib2 0 = (0, 1)
fib2 1 = (1, 1)
fib2 2 = (1, 2)
fib2 3 = (2, 3)

-- For larger numbers, derive fib2 n from fib2 (n `div` 2)
-- This takes advantage of the following identity:
--
--    fib(n) = fib(k)*fib(n-k-1) + fib(k+1)*fib(n-k)
--             where n > k
--               and k ≥ 0.
--
fib2 n =
    let (a, b) = fib2 (n `div` 2)
     in if even n
        then ((b-a)*a + a*b, a*a + b*b)
        else (a*a + b*b, a*b + b*(a+b))

真好 我爱Haskell。
阿伦

我在ghci中运行它。我印象深刻。Haskell非常适合这些类型的数学代码问题。
Undreren 2012年

1

C,朴素算法

很好奇,以前我没有用过gmp ...所以:

#include <stdio.h>
#include <stdlib.h>
#include <gmp.h>

int main(int argc, char *argv[]){
    int n = (argc>1)?atoi(argv[1]):0;

    mpz_t temp,prev,result;
    mpz_init(temp);
    mpz_init_set_ui(prev, 0);
    mpz_init_set_ui(result, 1);

    for(int i = 2; i <= n; i++) {
        mpz_add(temp, result, prev);
        mpz_swap(temp, result);
        mpz_swap(temp, prev);
    }

    printf("fib(%d) = %s\n", n, mpz_get_str (NULL, 10, result));

    return 0;
}

fib(100万)大约需要7秒钟的时间...因此该算法无法赢得比赛。


1

我在SBCL中实现了矩阵乘法方法(来自sicp,http: //sicp.org.ua/sicp/Exercise1-19 ),但大约需要30秒才能完成。我使用GMP将其移植到C,它在我的机器上约1.36秒内返回了正确的结果。它的速度与bootyby的回答一样快。

#include <gmp.h>
#include <stdio.h>

int main()
{
  int n = 20000000;

  mpz_t a, b, p, q, psq, qsq, twopq, bq, aq, ap, bp;
  int count = n;

  mpz_init_set_si(a, 1);
  mpz_init_set_si(b, 0);
  mpz_init_set_si(p, 0);
  mpz_init_set_si(q, 1);
  mpz_init(psq);
  mpz_init(qsq);
  mpz_init(twopq);
  mpz_init(bq);
  mpz_init(aq);
  mpz_init(ap);
  mpz_init(bp);

  while(count > 0)
    {
      if ((count % 2) == 0)
        {
          mpz_mul(psq, p, p);
          mpz_mul(qsq, q, q);
          mpz_mul(twopq, p, q);
          mpz_mul_si(twopq, twopq, 2);

          mpz_add(p, psq, qsq);    // p -> (p * p) + (q * q)
          mpz_add(q, twopq, qsq);  // q -> (2 * p * q) + (q * q) 
          count/=2;
        }

      else
       {
          mpz_mul(bq, b, q);
          mpz_mul(aq, a, q);
          mpz_mul(ap, a, p);
          mpz_mul(bp, b, p);

          mpz_add(a, bq, aq);      // a -> (b * q) + (a * q)
          mpz_add(a, a, ap);       //              + (a * p)

          mpz_add(b, bp, aq);      // b -> (b * p) + (a * q)

          count--;
       }

    }

  gmp_printf("%Zd\n", b);
  return 0;
}

1

Java:计算需要8秒,编写需要18秒

public static BigInteger fibonacci1(int n) {
    if (n < 0) explode("non-negative please");
    short charPos = 32;
    boolean[] buf = new boolean[32];
    do {
        buf[--charPos] = (n & 1) == 1;
        n >>>= 1;
    } while (n != 0);
    BigInteger a = BigInteger.ZERO;
    BigInteger b = BigInteger.ONE;
    BigInteger temp;
    do {
        if (buf[charPos++]) {
            temp = b.multiply(b).add(a.multiply(a));
            b = b.multiply(a.shiftLeft(1).add(b));
            a = temp;
        } else {
            temp = b.multiply(b).add(a.multiply(a));
            a = a.multiply(b.shiftLeft(1).subtract(a));
            b = temp;
        }
    } while (charPos < 32);
    return a;
}

public static void main(String[] args) {
    BigInteger f;
    f = fibonacci1(20000000);
    // about 8 seconds
    System.out.println(f.toString());
    // about 18 seconds
}

0

令人尴尬的缓慢。在我的计算机上,这只花了不到3分钟的时间。不过,这只是120个递归调用(添加缓存后)。请注意,这可能会占用大量内存(例如1.4 GiB)!

package main

import (
    "math/big"
    "fmt"
)

var cache = make(map[int64] *big.Int)

func fib_log_cache(n int64) *big.Int {
    if res, ok := cache[n]; ok {
        return res
    }
    res := fib_log(n)
    cache[n] = res
    return res
}

func fib_log(n int64) *big.Int {
    if n <= 1 {
        return big.NewInt(n)
    }

    if n % 2 == 0 {
        f_n_half := fib_log_cache(n/2)
        f_n_half_minus_one := fib_log_cache(n/2-1)
        res := new(big.Int).Lsh(f_n_half_minus_one, 1)
        res.Add(f_n_half, res)
        res.Mul(f_n_half, res)
        return res
    }
    f_n_half := fib_log_cache(n/2)
    f_n_half_plus_one := fib_log_cache(n/2+1)
    res := new(big.Int).Mul(f_n_half_plus_one, f_n_half_plus_one)
    tmp := new(big.Int).Mul(f_n_half, f_n_half)
    res.Add(res, tmp)
    return res
}

func main() {
    fmt.Println(fib_log(20000000))
}

我尝试使用go例程对它进行并行处理(在添加缓存之前),并且开始使用19 GiB内存:/
ReyCharles 2012年

-4

伪代码(我不知道你们在使用什么)

product = 1
multiplier = 3 // 3 is fibonacci sequence, but this can be any number, 
      // generating an infinite amount of sequences
y = 28 // the 2^x-1 term, so 2^28-1=1,284,455,535th term
for (int i = 1; int < y; i++) {
  product= sum*multiplier-1
  multiplier= multiplier^2-2
}
multiplier=multiplier-product // 2^28+1 1,284,455,537th 

这两个词花了我的计算机56个小时。我的电脑有点烂。我将在10月22日的文本文件中找到该号码。在我的连接上共享1.2个演出有点大。


1
您的回答让我感到困惑。伪码?但是你有时间吗?发布代码!语言无所谓!
2012年

这样,输出应该只有400万左右……
Wug 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.