Schreiben Sie die schnellsten Fibonacci

10

Dies ist eine weitere Herausforderung bei den Fibonacci-Zahlen.

Ziel ist es, die 20'000'000- te Fibonacii-Zahl so schnell wie möglich zu berechnen . Die Dezimalausgabe ist ungefähr 4 MiB groß; es beginnt mit:

28543982899108793710435526490684533031144309848579

Die MD5-Summe der Ausgabe ist

fa831ff5dd57a830792d8ded4c24c2cb

Sie müssen ein Programm einreichen, das die Anzahl während der Ausführung berechnet und das Ergebnis an legt stdout. Das schnellste Programm, gemessen auf meiner eigenen Maschine, gewinnt.

Hier sind einige zusätzliche Regeln:

  • Sie müssen den Quellcode und eine Binärdatei unter x64 Linux einreichen
  • Der Quellcode muss kürzer als 1 MiB sein. Im Falle einer Montage ist es auch akzeptabel, wenn nur die Binärdatei so klein ist.
  • Sie dürfen die zu berechnende Zahl nicht in Ihre Binärdatei aufnehmen, auch nicht getarnt. Die Anzahl muss zur Laufzeit berechnet werden.
  • Mein Computer hat zwei Kerne; Sie dürfen Parallelität verwenden

Ich habe eine kleine Implementierung aus dem Internet genommen, die in ungefähr 4,5 Sekunden ausgeführt wird. Es sollte nicht sehr schwierig sein, dies zu übertreffen, vorausgesetzt, Sie haben einen guten Algorithmus.

FUZxxl
quelle
1
Alter, alles wie Sage, das eine unbestimmte Float-Präzision hat, wird das Ding in weniger als 1/10 Sekunde laufen lassen. Es ist nur ein einfacher Ausdruck alsphi = (1+sqrt(5))/2
JBernardo
4
Können wir die Zahl hexadezimal ausgeben?
Keith Randall
2
@ Keith Nope. Das ist Teil der Spezifikation.
FUZxxl
3
Da es auf Ihrer CPU gemessen werden soll , könnten wir genauso gut weitere Informationen darüber haben, nicht wahr? Intel oder AMD? Größe der L1- und Anweisungs-Caches? Befehlssatzerweiterungen?
JB
2
Während ich es berechne, sind Ihre Startzeichenfolge und MD5 für die 20'000'000. Zahl, nicht nur für die 2'000'000.
JB

Antworten:

4

C mit GMP, 3,6 s

Götter, aber GMP macht Code hässlich. Mit einem Trick im Karatsuba-Stil konnte ich auf 2 Multiplikationen pro Verdopplungsschritt reduzieren. Jetzt, wo ich die Lösung von FUZxxl lese, bin ich nicht der erste, der auf die Idee kommt. Ich habe noch ein paar Tricks im Ärmel ... vielleicht probiere ich sie später aus.

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

Gebaut mit gcc -O3 m.c -o m -lgmp.

Standby
quelle
LOL. Abgesehen von einer Kennung, die genau meine Lösung ist :)
JB
@JB: ZUERST! : D
Standby
Behalte es;) Der nächste Trick in meinem Ärmel wird von Haskell mehr profitieren als von C.
JB
Der erste Trick in meinem Ärmel stieß auf einen GHC-Fehler. Drat. Ich muss auf die zweite zurückgreifen, deren Implementierung nicht so unterhaltsam ist, daher braucht es Zeit und Motivation.
JB
3,6 Sekunden auf meinem Computer.
FUZxxl
11

Salbei

Hmm, Sie scheinen anzunehmen, dass das schnellste ein kompiliertes Programm sein wird. Keine Binärdatei für dich!

print fibonacci(2000000)

Auf meinem Computer dauert es 0,10 CPU-Sekunden, 0,15 Wandsekunden.

Bearbeiten: Zeitgesteuert auf der Konsole anstelle des Notebooks

Standby
quelle
1
Meine Idee war nicht zu wissen, wie schnell Ihr CAS dies kann, sondern wie schnell Sie dies selbst codieren können.
FUZxxl
11
Für die Aufzeichnung habe ich dies nur als Smartass bezeichnet; Sie haben nicht gesagt, keine eingebauten Geräte zu verwenden.
Standby
5

Haskell

Dies ist mein eigener Versuch, obwohl ich den Algorithmus nicht selbst geschrieben habe. Ich habe es lieber von haskell.org kopiert und an Data.Vectordie berühmte Stream-Fusion angepasst :

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

Bei der Kompilierung mit GHC 7.0.3 und den folgenden Flags dauert dies ungefähr 4,5 Sekunden:

ghc-O3-fllvm fib.hs.
FUZxxl
quelle
Seltsam ... Ich musste 20000000 in 40000000 ändern, damit die erwartete Nummer gedruckt wurde.
JB
Erwischt. Sollte enumFromStepN (s-1)statt seinenumFromStepN s
JB
@JB Entschuldigung für all diese Verwirrung. Ich habe das Programm zunächst mit verschiedenen Werten getestet, um eine einigermaßen große Zahl zu erhalten, und die Ausgabe in verschiedenen Dateien gespeichert. Aber wie ich sie verwirrt habe. Ich habe die Nummer aktualisiert, um dem gewünschten Ergebnis zu entsprechen.
FUZxxl
@boothby Nein, ich habe nicht die gewünschte Fibonacci-Nummer geändert, sondern die Referenzausgabe, was falsch war.
FUZxxl
Randnotiz: Auf meinem Computer sind es ungefähr 1,5 Sekunden, aber weder LLVM noch Data.Vector scheinen einen signifikanten Vorteil zu bringen.
JB
4

KUH

 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

Muhen! (Dauert eine Weile. Trink etwas Milch ...)

Timtech
quelle
1
Hinweis: Obwohl dies wirklich funktioniert, wird es wahrscheinlich nie 20.000.000 erreichen ...
Timtech
2

Mathematica, interpretiert:

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

Zeitlich festgelegt:

0.032 secs on my poor man's laptop.

Und natürlich keine Binärdatei.

Dr. Belisarius
quelle
Druckt nicht auf stdout.
Standby
@ Boothby Falsch. Es schreibt in die Standardausgabe, wenn Sie die Befehlszeilenschnittstelle verwenden. Siehe zum Beispiel stackoverflow.com/questions/6542537/…
Dr. belisarius
Nein, ich verwende die Befehlszeilenschnittstelle, Version 6.0. Selbst bei Verwendung -batchoutputwerden nur Timing-Informationen und nicht die Fibonacci-Nummer gedruckt .
Standby
Entschuldigung, kann nicht reproduzieren, da ich keine Mathematik habe.
FUZxxl
5
curl 'http://www.wolframalpha.com/input/?i=Fibonacci%5B2+10^6%5D' | grep 'Decimal approximation:' | sed ... Es läuft in konstanter Zeit in Bezug auf die Geschwindigkeit Ihrer Internetverbindung. ;-)
ESultanik
2

Ocaml, 0,856s auf meinem Laptop

Benötigt die Zarith-Bibliothek. Ich habe Big_int verwendet, aber es ist langsam im Vergleich zu Zarith. Es dauerte 10 Minuten mit dem gleichen Code! Die meiste Zeit wurde damit verbracht , die verdammte Nummer zu drucken (9½ Minuten oder so)!

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

Ich kann nicht glauben, wie viel Unterschied die Bibliothek gemacht hat!

ReyCharles
quelle
1
Zum Vergleich: Auf meinem Laptop dauert die Lösung von @ Standby 0,875 Sekunden. Es scheint, dass der Unterschied vernachlässigbar ist. Außerdem ist mein Laptop anscheinend schnell : o
ReyCharles
1

Haskell

Auf meinem System läuft dies fast so schnell wie die Antwort von FUZxxl (~ 18 Sekunden statt ~ 17 Sekunden).

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))
Joey Adams
quelle
Nett. Ich liebe Haskell.
Arlen
Ich habe das in ghci ausgeführt. Ich war ziemlich beeindruckt. Haskell eignet sich hervorragend für diese Art von mathematischen Codeproblemen.
Undreren
1

C, naiver Algorithmus

War neugierig und ich hatte gmp noch nie benutzt ... also:

#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 Million) dauert ungefähr 7 Sekunden ... also wird dieser Algorithmus das Rennen nicht gewinnen.

Baby-Kaninchen
quelle
1

Ich habe die Matrixmultiplikationsmethode (von sicp, http://sicp.org.ua/sicp/Exercise1-19 ) in SBCL implementiert, aber es dauert ungefähr 30 Sekunden, bis sie fertig ist. Ich habe es mit GMP nach C portiert und es gibt auf meinem Computer in ca. 1,36 Sekunden das richtige Ergebnis zurück. Es ist ungefähr so ​​schnell wie die Antwort von Standby.

#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;
}
Erik Haliewicz
quelle
1

Java: 8 Sekunden zum Berechnen, 18 Sekunden zum Schreiben

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
}
averykhoo
quelle
0

Gehen

Es ist peinlich langsam. Auf meinem Computer dauert es etwas weniger als 3 Minuten. Es sind jedoch nur 120 rekursive Aufrufe (nach dem Hinzufügen des Caches). Beachten Sie, dass dies viel Speicherplatz beanspruchen kann (wie 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))
}
ReyCharles
quelle
Ich habe versucht, es (vor dem Hinzufügen des Caches) mithilfe von Go-Routinen zu parallelisieren, und es wurden 19
GB
-4

Pseudocode (Ich weiß nicht, was ihr benutzt)

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 

Mein Computer brauchte 56 Stunden, um diese beiden Begriffe zu erledigen. Mein Computer ist irgendwie beschissen. Ich werde die Nummer am 22. Oktober in einer Textdatei haben. 1.2 Gigs sind ein bisschen groß, um sie auf meiner Verbindung zu teilen.

Thomas Olson
quelle
1
Ihre Antwort verwirrt mich. Pseudocode? Und doch hast du Timings? Poste den Code! Sprache spielt keine Rolle!
Standby
Das, und die Ausgabe soll nur 4 Millionen oder so Ziffern sein ...
Wug