Scrivi il Fibonacci più veloce


10

Questa è un'altra sfida sui numeri di Fibonacci.

L'obiettivo è quello di calcolare il 20'000'000 esimo numero di Fibonacii il più velocemente possibile. L'output decimale è di circa 4 MiB di grandi dimensioni; inizia con:

28543982899108793710435526490684533031144309848579

La somma MD5 dell'output è

fa831ff5dd57a830792d8ded4c24c2cb

Devi inviare un programma che calcola il numero mentre è in esecuzione e inserisce il risultato stdout. Il programma più veloce, misurato sulla mia macchina, vince.

Ecco alcune regole aggiuntive:

  • Devi inviare il codice sorgente e un binario eseguibile su un Linux x64
  • Il codice sorgente deve essere inferiore a 1 MiB, in caso di assemblaggio è accettabile anche se solo il binario è così piccolo.
  • Non devi includere il numero da calcolare nel tuo file binario, anche in modo mascherato. Il numero deve essere calcolato in fase di esecuzione.
  • Il mio computer ha due core; ti è permesso usare il parallelismo

Ho preso una piccola implementazione da Internet che dura circa 4,5 secondi. Non dovrebbe essere molto difficile batterlo, supponendo che tu abbia un buon algoritmo.


1
Amico, qualcosa come Sage che ha una precisione del galleggiante indeterminata eseguirà quella cosa in meno di 1/10 di secondo. È solo una semplice espressione comephi = (1+sqrt(5))/2
JBernardo,

4
Possiamo produrre il numero in esadecimale?
Keith Randall,

2
@Keith Nope. Fa parte delle specifiche.
FUZxxl,

3
Dal momento che deve essere misurato sulla tua CPU, potremmo anche avere qualche informazione in più, no? Intel o AMD? Dimensioni della cache L1 e delle istruzioni? Estensioni set di istruzioni?
JB

2
Mentre lo computo, la stringa iniziale e MD5 sono per il numero 20'000'000, non per il semplice 2'000'000.
JB

Risposte:


4

C con GMP, 3.6s

Dei, ma GMP rende il codice brutto. Con un trucco in stile Karatsuba, sono riuscito a ridurre a 2 moltiplicazioni per passaggio raddoppiato. Ora che sto leggendo la soluzione di FUZxxl, non sono il primo ad avere l'idea. Ho qualche altro asso nella manica ... forse li proverò più tardi.

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

Costruito con gcc -O3 m.c -o m -lgmp.


LOL. A parte un nome identificativo, questa è esattamente la mia soluzione :)
JB

@JB: PRIMO! : D
stand dal

Keep it;) Il prossimo asso nella manica trarrà beneficio da Haskell più che da C.
JB

Per prima cosa, mi sono rimboccato la manica in un bug GHC. Drat. Dovrò tornare al secondo, che non è così divertente da implementare, quindi ci vorrà tempo e motivazione.
JB

3,6 secondi sulla mia macchina.
FUZxxl,

11

saggio

Sembra che tu pensi che il più veloce sia un programma compilato. Nessun binario per te!

print fibonacci(2000000)

Sulla mia macchina, ci vogliono 0,10 cpu secondi, 0,15 secondi a parete.

modifica: temporizzato sulla console, anziché sul notebook


1
La mia idea non era di sapere quanto velocemente il tuo CAS può farlo, ma piuttosto quanto velocemente puoi codificarlo da solo.
FUZxxl,

11
Per la cronaca, ho appena messo questo per essere una smartass; non hai detto di non usare i builtin.
stand dal

5

Haskell

Questo è il mio tentativo, anche se non ho scritto l'algoritmo da solo. Piuttosto l'ho copiato da haskell.org e l' ho adattato per l'uso Data.Vectorcon il suo famoso stream fusion:

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

Questa operazione richiede circa 4,5 secondi se compilata con GHC 7.0.3 e i seguenti flag:

ghc -O3 -fllvm fib.hs

Strano ... Ho dovuto cambiare da 20000000 a 40000000 per farlo stampare il numero previsto.
JB

Gotcha. Dovrebbe essere enumFromStepN (s-1)invece dienumFromStepN s
JB

@JB Ci scusiamo per tutta questa confusione. Inizialmente ho testato il programma con valori diversi per ottenere un numero ragionevolmente grande e ho salvato l'output in file diversi. Ma alcuni come li ho confusi. Ho aggiornato il numero in modo che corrisponda al risultato desiderato.
FUZxxl,

@boothby No, non ho modificato il numero di fibonacci desiderato, ma piuttosto l'output di riferimento, che era sbagliato.
FUZxxl,

Nota a margine: sono circa 1,5 secondi sulla mia macchina, ma né LLVM né Data.Vector sembrano portare alcun vantaggio significativo.
JB

4

MUCCA

 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

Muggire! (Ci vuole un po '. Bevi del latte ...)


1
Nota: anche se funziona davvero, probabilmente non raggiungerà mai i 20.000.000 ...
Timtech,

2

Mathematica, interpretato:

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

temporizzata:

0.032 secs on my poor man's laptop.

E ovviamente, nessun binario.


Non stampa su stdout.
stand dal

@boothby Wrong. Scrive nell'output standard se si utilizza l'interfaccia della riga di comando. Si veda ad esempio stackoverflow.com/questions/6542537/...
Dr. Belisario

No, sto usando l'interfaccia della riga di comando, versione 6.0. Anche usando -batchoutput, stampa solo le informazioni di temporizzazione e non il numero di Fibonacci.
stand dal

Scusa, non riesco a riprodurre poiché non ho matematica.
FUZxxl,

5
curl 'http://www.wolframalpha.com/input/?i=Fibonacci%5B2+10^6%5D' | grep 'Decimal approximation:' | sed ... Funziona a tempo costante rispetto alla velocità della tua connessione Internet. ;-)
ESultanik il

2

Ocaml, 0.856s sul mio laptop

Richiede la libreria zarith. Ho usato Big_int ma è un cane lento rispetto a zarith. Ci sono voluti 10 minuti con lo stesso codice! La maggior parte del tempo è stato impiegato per stampare quel dannato numero (circa 9½ minuti)!

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

Non riesco a credere che differenza abbia fatto la biblioteca!


1
Per confronto la soluzione di @ boothby impiega 0,875 secondi per funzionare sul mio laptop. Sembra che la differenza sia neglible. Inoltre, a quanto pare il mio laptop è veloce : o
ReyCharles,

1

Haskell

Sul mio sistema, funziona quasi alla stessa velocità della risposta di FUZxxl (~ 18 secondi invece di ~ 17 secondi).

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

Bello. Adoro Haskell.
Arlen,

L'ho gestito in ghci. Sono rimasto piuttosto colpito. Haskell è eccezionale per questi tipi di problemi di codice matematico.
Undreren,

1

C, algoritmo ingenuo

Ero curioso e non avevo mai usato gmp prima ... quindi:

#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 milione) impiega circa 7 secondi ... quindi questo algoritmo non vincerà la gara.


1

Ho implementato il metodo di moltiplicazione della matrice (da sicp, http://sicp.org.ua/sicp/Exercise1-19 ) in SBCL ma ci vogliono circa 30 secondi per terminare. L'ho portato su C usando GMP e restituisce il risultato corretto in circa 1,36 secondi sulla mia macchina. È veloce quanto la risposta di Boothby.

#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 secondi per il calcolo, 18 secondi per la scrittura

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

Partire

È imbarazzantemente lento. Sul mio computer ci vogliono poco meno di 3 minuti. Sono solo 120 chiamate ricorsive, tuttavia (dopo aver aggiunto la cache). Nota che questo può usare molta memoria (come 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))
}

Ho provato a parallelizzarlo (prima di aggiungere la cache) usando go routine e ha iniziato a usare 19 GiB di memoria: /
ReyCharles

-4

pseudo codice (non so cosa state usando voi ragazzi)

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 

Il mio computer ha impiegato 56 ore per fare quei due termini. Il mio computer è un po 'schifoso. Avrò il numero in un file di testo il 22 ottobre. 1.2 concerti è un po 'grande da condividere sulla mia connessione.


1
Sono confuso dalla tua risposta. Pseudocodice? Eppure hai degli orari? Pubblica il codice! La lingua non ha importanza!
stand dal

Quello, e l'uscita dovrebbe essere solo di circa 4 milioni di cifre ...
Wug,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.