가장 빠른 피보나치 쓰기


10

이것은 피보나치 수에 관한 또 다른 도전입니다.

목표는 가능한 빨리 20'000'000 번째 피보나치 수 를 계산하는 것 입니다. 10 진수 출력은 약 4MiB입니다. 그것은 시작합니다 :

28543982899108793710435526490684533031144309848579

출력의 MD5 합계는

fa831ff5dd57a830792d8ded4c24c2cb

실행하는 동안 숫자를 계산하고 결과를에 넣는 프로그램을 제출해야합니다 stdout. 내 컴퓨터에서 측정 한 가장 빠른 프로그램이 승리합니다.

몇 가지 추가 규칙은 다음과 같습니다.

  • x64 Linux에서 실행 가능한 소스 코드와 바이너리를 제출해야합니다.
  • 소스 코드는 1MiB보다 짧아야합니다. 어셈블리의 경우 바이너리 만 작은 경우에도 허용됩니다.
  • 위장 된 방식으로도 이진수로 계산할 숫자를 포함해서는 안됩니다. 숫자는 런타임에 계산해야합니다.
  • 내 컴퓨터에는 두 개의 코어가 있습니다. 당신은 병렬 처리를 사용할 수 있습니다

나는 약 4.5 초 만에 실행되는 인터넷에서 작은 구현을했습니다. 좋은 알고리즘이 있다고 가정하면이를이기는 것은 어렵지 않습니다.


1
플로트 정밀도가 불확실한 Sage와 같은 것은 초당 1/10 초 미만으로 실행됩니다. 이 같은 단순한 표현이다phi = (1+sqrt(5))/2
JBernardo

4
숫자를 16 진수로 출력 할 수 있습니까?
Keith Randall

2
@ 키이스 노페. 그것은 사양의 일부입니다.
FUZxxl

3
이 측정 될 이래로 당신의 CPU, 우리는뿐만 아니라 우리는 할 수 없었다, 그것에 대해 좀 더 많은 정보를 가지고 있는가? 인텔 또는 AMD? L1 및 명령어 캐시의 크기는? 명령어 세트 확장?
JB

2
내가 계산할 때 시작 문자열과 MD5는 2'000'000이 아니라 20'000'000의 숫자입니다.
JB

답변:


4

C, GMP, 3.6 초

신이지만 GMP는 코드를 못 생겼습니다. 가라 쓰바 스타일의 속임수를 사용하여 두 배의 단계마다 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.


LOL. 식별자 명명과는 별도로, 그것은 나의 해결책입니다. :)
JB

@JB : 먼저! : D
boothby

그것을 유지해라.) 내 소매의 다음 속임수는 Haskell로부터 C보다 더 많은 혜택을 얻을 것이다.
JB

먼저 GHC 버그에 부딪힌 내 소매를 속인다. 드랏. 원격으로 구현하기가 재미없는 두 번째 항목으로 돌아 가야하므로 시간과 동기가 필요합니다.
JB

내 컴퓨터에서 3.6 초
FUZxxl

11

세이지

흠, 당신은 가장 빠른 것이 컴파일 된 프로그램이라고 가정하는 것 같습니다. 바이너리가 없습니다!

print fibonacci(2000000)

내 컴퓨터에서는 0.10 cpu 초, 0.15 벽 초가 걸립니다.

편집 : 노트북 대신 콘솔에서 시간이 초과되었습니다.


1
내 생각은 CAS가 얼마나 빨리이를 수행 할 수 있는지를 알기위한 것이 아니라 오히려 직접 코딩 할 수있는 속도를 알기위한 것이 었습니다.
FUZxxl

11
기록을 위해, 나는 이것을 현명한 것으로 생각했다. 당신은 내장을 사용하지 말라고 말하지 않았습니다.
boothby

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

참고 사항 : 내 컴퓨터에서는 약 1.5 초이지만 Data.Vector가 아닌 LLVM은 중요한 이점을 제공하지 않는 것 같습니다.
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.
boothby

@boothby 잘못되었습니다. 명령 행 인터페이스를 사용하면 표준 출력에 기록합니다. 예를 들어 stackoverflow.com/questions/6542537/…
Dr. belisarius

아니요, 커맨드 라인 인터페이스 버전 6.0을 사용하고 있습니다. 를 사용하더라도 피보나치 번호가 아닌-batchoutput 타이밍 정보 만 인쇄합니다 .
boothby

수학이 없어서 재현 할 수 없습니다.
FUZxxl

5
curl 'http://www.wolframalpha.com/input/?i=Fibonacci%5B2+10^6%5D' | grep 'Decimal approximation:' | sed ... 인터넷 연결 속도와 관련하여 일정한 시간에 실행됩니다. ;-)
ESultanik

2

내 노트북에 Ocaml, 0.856s

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.875 초 내 랩톱에서 실행됩니다. 그 차이는 무시할만한 것 같습니다. 또한 분명히 내 노트북은 빠릅니다 : o
ReyCharles

1

하스켈

내 시스템에서 이것은 FUZxxl의 답변 만큼 빠릅니다 (~ 17 초 대신 ~ 18 초).

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))

좋은. 나는 하스켈을 좋아합니다.
Arlen

나는 이것을 ghci에서 달렸다. 꽤 감동했습니다. Haskell은 이러한 유형의 수학 코드 문제에 적합합니다.
Undreren

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 초 안에 올바른 결과를 반환합니다. 부스비의 대답만큼 빠릅니다.

#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

자바 : 계산하는 데 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.4Gi와 같은 많은 메모리를 사용할 수 있습니다!

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))
}

나는 이동 루틴을 사용 (캐시를 추가하기 전에)를 병렬 시도하고 메모리의 19 지브를 사용하기 시작 : /
ReyCharles

-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
나는 당신의 대답에 혼란스러워합니다. 의사 코드? 아직 타이밍이 있습니까? 코드를 게시하십시오! 언어는 중요하지 않습니다!
boothby

즉, 결과는 4 백만 정도의 숫자
여야합니다
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.