En hızlı Fibonacci'yi yazın


10

Bu Fibonacci sayıları ile ilgili bir başka zorluktur.

Amaç 20'000'000 hesaplamak etmektir inci mümkün olduğunca hızlı olarak Fibonacii numarası. Ondalık çıktı yaklaşık 4 MiB büyüklüğündedir; ile başlar:

28543982899108793710435526490684533031144309848579

Çıktının MD5 toplamı

fa831ff5dd57a830792d8ded4c24c2cb

Çalışırken sayıyı hesaplayan ve sonucu veren bir program göndermeniz gerekir stdout. Kendi makinemde ölçülen en hızlı program kazanıyor.

İşte bazı ek kurallar:

  • Kaynak kodu ve x64 Linux'ta çalıştırılabilir bir ikili dosya göndermeniz gerekir
  • Kaynak kodun 1 MiB'den kısa olması gerekir, montaj durumunda sadece ikili dosya çok küçükse de kabul edilebilir.
  • Gizli olarak bile olsa, hesaplanacak sayıyı ikili dosyaya eklememelisiniz. Sayı çalışma zamanında hesaplanmalıdır.
  • Bilgisayarımın iki çekirdeği var; Paralellik kullanma izniniz var

İnternetten yaklaşık 4,5 saniyede çalışan küçük bir uygulama aldım. İyi bir algoritmaya sahip olduğunuzu varsayarak bunu yenmek çok zor olmamalıdır.


1
Dostum, belirsiz şamandıra hassasiyetine sahip Sage gibi bir şey, o şeyi saniyenin 1 / 10'undan daha az bir sürede çalıştırır. Bu sadece basit bir ifadedirphi = (1+sqrt(5))/2
JBernardo

4
Sayıları onaltılı olarak verebilir miyiz?
Keith Randall

2
@Keith Nope. Bu özelliklerin bir parçası.
FUZxxl

3
Üzerinde ölçülecek olduğu için senin CPU, biz de biz could not, bu konuda biraz daha fazla bilgi sahibi olabilir? Intel mi AMD mi? L1'in boyutu ve talimat önbellekleri? Komut kümesi uzantıları?
JB

2
Bunu hesaplarken, başlangıç ​​dizeniz ve MD5'iniz sadece 2'000'000 değil, 20'000'000. sayı içindir.
JB

Yanıtlar:


4

GMP'li C, 3.6s

Tanrılar, ama GMP çirkin kodlar yapıyor. Karatsuba tarzı bir hile ile, iki katlama adımı başına 2 çarpımı kesmeyi başardım. Şimdi FUZxxl'in çözümünü okuduğuma göre, ilk fikre sahip değilim. Kolumda birkaç numara daha var ... belki daha sonra deneyeceğim.

#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");
}

İle inşa edilmiştir gcc -O3 m.c -o m -lgmp.


LOL. Bir tanımlayıcı adlandırma dışında, tam olarak benim çözümüm :)
JB

@JB: İLK! : D
boothby

Saklayın;) Kolumdaki bir sonraki numara Haskell'den C'den daha fazla faydalanacak.
JB

İlk önce bir GHC böcek çarptı benim kol kandırmak. Lanet olsun. İkincisine geri dönmem gerekecek, bu da uygulanması çok eğlenceli değil, bu yüzden zaman ve motivasyon alacak.
JB

Makinemde 3,6 sn.
FUZxxl

11

adaçayı

Hmm, en hızlısının derlenmiş bir program olacağını sanıyorsunuz. Sizin için ikili değil!

print fibonacci(2000000)

Makinemde 0.10 cpu saniye, 0.15 duvar saniye sürüyor.

edit: dizüstü yerine konsolda zamanlanmış


1
Benim fikrim, CAS'ınızın bunu ne kadar hızlı yapabileceğini değil, bunu ne kadar hızlı kodlayabileceğinizi bilmekti.
FUZxxl

11
Kayıt için, bunu sadece bir zeki haline getirdim; yerleşik kullanmamanızı söylemediniz.
boothby

5

Haskell

Algoritmayı kendi başıma yazmamış olmama rağmen bu benim kendi denemem. Haskell.org'dan kopyaladım Data.Vectorve ünlü akış füzyonuyla kullanmak için uyarladım :

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 ve aşağıdaki bayraklarla derlendiğinde bu yaklaşık 4.5 saniye sürer:

ghc -O3 -fllvm fib.hs

Tuhaf ... Beklenen sayıyı yazdırabilmek için 20000000'ü 40000000'e değiştirmem gerekiyordu.
JB

Anladım. Bunun enumFromStepN (s-1)yerineenumFromStepN s
JB

@JB Tüm bu karışıklıklar için özür dilerim. Başlangıçta oldukça büyük bir sayı elde etmek için programı farklı değerlerle test ettim ve çıktıyı farklı dosyalara kaydettim. Ama onları nasıl karıştırdım. Numarayı istenen sonuca uyacak şekilde güncelledim.
FUZxxl

@boothby Hayır, istenen fibonacci numarasını değil, yanlış olan referans çıktısını değiştirdim.
FUZxxl

Yan not: Makinemde yaklaşık 1,5 sn, ancak ne Veri değil LLVM. Vektör önemli bir avantaj sağlıyor.
JB

4

İNEK

 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

Moo! (Biraz zaman alır. Biraz süt iç ...)


1
Not: Bu gerçekten işe yarıyor olsa da, muhtemelen asla 20.000.000'e ulaşmayacaktır ...
Timtech

2

Mathematica, yorumlandı:

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

Zamanlı:

0.032 secs on my poor man's laptop.

Ve tabii ki, ikili değil.


Yazdırmıyor stdout.
boothby

@boothby Yanlış. Komut satırı arabirimini kullanırsanız standart çıktıya yazar. Örneğin, bkz. Stackoverflow.com/questions/6542537/…
Dr. belisarius

Hayır, 6.0 sürümünün komut satırı arayüzünü kullanıyorum. Kullansa bile , Fibonacci numarasını değil , -batchoutputsadece zamanlama bilgilerini yazdırır .
boothby

Üzgünüm, matematiksel olmadığım için çoğaltılamıyor.
FUZxxl

5
curl 'http://www.wolframalpha.com/input/?i=Fibonacci%5B2+10^6%5D' | grep 'Decimal approximation:' | sed ... İnternet bağlantınızın hızına göre sürekli çalışır. ;-)
ESultanik

2

Ocaml, dizüstü bilgisayarımda 0.856s

Zarith kütüphanesini gerektirir. Big_int kullandım ama zarith ile karşılaştırıldığında yavaş köpek. Aynı kod ile 10 dakika sürdü! Çoğu zaman harcandı baskı (9½ dakika kadar) lanet numarasını!

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

Kütüphanenin ne kadar fark yarattığına inanamıyorum!


1
Karşılaştırma için @ boothby'nin çözümü dizüstü bilgisayarımda çalıştırmak için 0.875s alır. Fark göz ardı edilebilir. Ayrıca, görünüşe göre dizüstü bilgisayarım hızlı : o
ReyCharles

1

Haskell

Sistemimde, bu neredeyse FUZxxl'in cevabı kadar hızlı çalışır (~ 17 saniye yerine ~ 18 saniye).

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

Güzel. Haskell'i seviyorum.
Arlen

Bunu ghci'de çalıştırdım. Çok etkilendim. Haskell bu tür matematiksel kod problemleri için harikadır.
Undreren

1

C, saf algoritma

Merak ediyordum ve daha önce gmp kullanmamıştım ... yani:

#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 (1 milyon) yaklaşık 7 saniye sürer ... yani bu algoritma yarışı kazanmaz.


1

SBCL'de matris çarpma yöntemini (sicp, http://sicp.org.ua/sicp/Exercise1-19 ) uyguladım, ancak tamamlanması yaklaşık 30 saniye sürüyor. GMP kullanarak C'ye taşıdım ve makinemde yaklaşık 1.36 saniye içinde doğru sonucu döndürdü. Boothby'nin cevabı kadar hızlı.

#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: Hesaplamak için 8 saniye, yazmak için 18 saniye

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

Git

Utanç verici derecede yavaş. Bilgisayarımda 3 dakikadan biraz daha az sürüyor. Sadece 120 özyinelemeli çağrı (önbellek ekledikten sonra). Bunun çok fazla bellek kullanabileceğini unutmayın (1.4 GiB gibi)!

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

Git rutinleri kullanarak (önbellek eklemeden önce) paralelleştirmeyi denedim ve 19 GiB bellek kullanmaya başladı: /
ReyCharles

-4

sözde kod (ne kullandığınızı bilmiyorum)

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 

Bilgisayarımın bu iki terimi yapması 56 saat sürdü. Bilgisayarım biraz berbat. Numarayı 22 Ekim'de bir metin dosyasında alacağım. 1.2 konserler bağlantımda paylaşacak kadar büyük.


1
Cevabınızla kafam karıştı. Pseudocode? Yine de zamanlamalarınız var mı? Kodu gönderin! Dil önemli değil!
boothby

Bu ve çıktının sadece 4 milyon basamaklı olması gerekiyordu ...
Wug
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.