เขียนฟีโบนักชีที่เร็วที่สุด


10

นี่เป็นความท้าทายอีกประการเกี่ยวกับตัวเลขฟีโบนักชี

เป้าหมายคือการคำนวณตัวเลข Fibonacii ที่ 20'000'000 thให้เร็วที่สุด เอาต์พุตทศนิยมมีขนาดใหญ่ประมาณ 4 MiB มันเริ่มต้นด้วย:

28543982899108793710435526490684533031144309848579

ผลรวม MD5 ของเอาต์พุตคือ

fa831ff5dd57a830792d8ded4c24c2cb

stdoutคุณต้องส่งโปรแกรมที่คำนวณจำนวนในขณะที่ทำงานและทำให้ผลไปยัง โปรแกรมที่เร็วที่สุดซึ่งวัดจากเครื่องของฉันเองเป็นผู้ชนะ

นี่คือกฎเพิ่มเติม:

  • คุณต้องส่งซอร์สโค้ดและไบนารีที่รันได้บน x64 Linux
  • ซอร์สโค้ดจะต้องสั้นกว่า 1 MiB ในกรณีของการประกอบมันยังเป็นที่ยอมรับหากไบนารีนั้นเล็ก
  • คุณต้องไม่รวมจำนวนที่จะคำนวณในไบนารีของคุณแม้จะอยู่ในรูปแบบที่ปลอมแปลง ต้องคำนวณจำนวนที่รันไทม์
  • คอมพิวเตอร์ของฉันมีสองคอร์ คุณได้รับอนุญาตให้ใช้ขนาน

ฉันใช้งานขนาดเล็กจากอินเทอร์เน็ตซึ่งใช้เวลาประมาณ 4.5 วินาที ไม่ควรยากที่จะเอาชนะสิ่งนี้โดยสมมติว่าคุณมีอัลกอริทึมที่ดี


1
เพื่อนอะไรเช่น Sage ที่มีความแม่นยำลอยไม่แน่นอนจะทำงานในสิ่งที่น้อยกว่า 1 / 10th ของวินาที มันเป็นเพียงการแสดงออกอย่างง่าย ๆphi = (1+sqrt(5))/2
JBernardo

4
เราสามารถส่งออกตัวเลขเป็นฐานสิบหกได้หรือไม่?
Keith Randall

2
@ Keith Nope นั่นเป็นส่วนหนึ่งของข้อมูลจำเพาะ
FUZxxl

3
เนื่องจากมันจะถูกวัดบนCPU ของคุณเราอาจมีข้อมูลเพิ่มเติมเกี่ยวกับมันใช่ไหม? Intel หรือ AMD ขนาดของ L1 และแคชคำสั่ง? ส่วนขยายชุดคำสั่ง?
JB

2
ในขณะที่ฉันคำนวณมันสตริงเริ่มต้นและ MD5 ของคุณสำหรับหมายเลข 20'000'000 ที่ไม่ใช่ไม่ใช่ 2'000'000
JB

คำตอบ:


4

C กับ GMP, 3.6s

พระเจ้า แต่ GMP สร้างรหัสที่น่าเกลียด ด้วยเคล็ดลับสไตล์ Karatsuba ฉันสามารถลดจำนวนทวีคูณเป็นสองเท่าต่อขั้นตอน ตอนนี้ฉันกำลังอ่านวิธีแก้ปัญหาของ FUZxxl ฉันไม่ใช่คนแรกที่มีความคิด ฉันมีเคล็ดลับเพิ่มเติมอีกสองสามอย่างบนแขนเสื้อของฉัน ... บางทีฉันอาจลอง 'em ในภายหลัง

#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
บูธโดย

ให้มัน;) เคล็ดลับต่อไปขึ้นที่แขนของฉันจะได้รับประโยชน์จาก Haskell มากกว่าจาก C
JB

ขั้นแรกหลอกแขนเสื้อของฉันชนในข้อผิดพลาด GHC DRAT ฉันจะต้องถอยกลับไปยังอันดับที่สองซึ่งไม่ใช่เรื่องสนุกสำหรับการติดตั้งจากระยะไกลดังนั้นมันจะต้องใช้เวลาและแรงจูงใจ
JB

3.6 วินาทีบนเครื่องของฉัน
FUZxxl

11

ปราชญ์

อืมดูเหมือนว่าคุณคิดว่าโปรแกรมที่เร็วที่สุดจะเป็นคอมไพล์ ไม่มีเลขฐานสองสำหรับคุณ!

print fibonacci(2000000)

บนเครื่องของฉันใช้เวลา 0.10 cpu วินาที, 0.15 วินาทีวินาที

แก้ไข: หมดเวลาบนคอนโซลแทนโน้ตบุ๊ก


1
ความคิดของฉันไม่ได้รู้ว่า CAS ของคุณสามารถทำสิ่งนี้ได้เร็วแค่ไหน แต่คุณสามารถเขียนโค้ดนี้ด้วยตัวเองได้เร็วแค่ไหน
FUZxxl

11
สำหรับเร็กคอร์ดฉันเพิ่งจะทำให้มันกลายเป็น smartass; คุณไม่ได้บอกว่าจะไม่ใช้บิวอิน
บูธโดย

5

Haskell

นี่คือความพยายามของฉันเองแม้ว่าฉันจะไม่ได้เขียนอัลกอริทึมด้วยตัวเอง ฉันค่อนข้างคัดลอกมาจาก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

ใช้เวลาประมาณ 4.5 วินาทีเมื่อรวบรวมด้วย GHC 7.0.3 และการตั้งค่าสถานะต่อไปนี้:

ghc -O3 -fllvm fib.hs

แปลก ... ฉันต้องการเปลี่ยน 20000000 เป็น 40000000 เพื่อให้ได้จำนวนที่ต้องการ
JB

Gotcha ควรจะenumFromStepN (s-1)แทนenumFromStepN s
JB

@JB ขออภัยด้วยความสับสนทั้งหมดนี้ ฉันเริ่มทดสอบโปรแกรมด้วยค่าที่แตกต่างกันเพื่อให้ได้จำนวนที่มากพอสมควรและบันทึกผลลัพธ์เป็นไฟล์ต่าง ๆ แต่บางวิธีฉันสับสนพวกเขา ฉันได้อัปเดตหมายเลขให้ตรงกับผลลัพธ์ที่ต้องการ
FUZxxl

@ ค้อนโดยไม่ฉันไม่ได้เปลี่ยนหมายเลขฟีโบนักชีที่ต้องการ แต่เป็นเอาท์พุทอ้างอิงซึ่งผิด
FUZxxl

หมายเหตุด้านข้าง: มันเกี่ยวกับ 1.5s บนเครื่องของฉัน แต่ไม่ใช่ 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ไม่ได้พิมพ์ไป
บูธโดย

@ boothby ผิด มันเขียนไปยังเอาต์พุตมาตรฐานหากคุณใช้อินเตอร์เฟสบรรทัดคำสั่ง ดูตัวอย่างstackoverflow.com/questions/6542537/…
ดร. เบลิซาเรียส

ไม่ฉันใช้อินเตอร์เฟสบรรทัดคำสั่งเวอร์ชัน 6.0 แม้จะใช้-batchoutputเพียงพิมพ์ข้อมูลเวลาและไม่ใช่หมายเลขฟีโบนักชี
บูธโดย

ขออภัยทำซ้ำไม่ได้เนื่องจากฉันไม่มีวิชาคณิตศาสตร์
FUZxxl

5
curl 'http://www.wolframalpha.com/input/?i=Fibonacci%5B2+10^6%5D' | grep 'Decimal approximation:' | sed ... มันทำงานในเวลาคงที่เกี่ยวกับความเร็วของการเชื่อมต่ออินเทอร์เน็ตของคุณ ;-)
ESultanik

2

Ocaml, 0.856s บนแล็ปท็อปของฉัน

ต้องการไลบรารีซาริ ธ ฉันใช้ 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

Haskell

ในระบบของฉันสิ่งนี้เกือบจะเร็วเท่ากับคำตอบของ 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))

ดี ฉันรักแฮสเคลล์
Arlen

ฉันทำสิ่งนี้ใน ghci ฉันประทับใจมาก Haskell เหมาะสำหรับปัญหารหัสทางคณิตศาสตร์ประเภทนี้
Undreren

1

C อัลกอริทึมไร้เดียงสา

อยากรู้อยากเห็นและฉันไม่เคยใช้ GPMP มาก่อน ... ดังนั้น:

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

ตอแหล (1 ล้าน) ใช้เวลาประมาณ 7 วินาที ... ดังนั้นอัลกอริทึมนี้จะไม่ชนะการแข่งขัน


1

ฉันใช้วิธีการคูณเมทริกซ์ (จาก sicp, http://sicp.org.ua/sicp/Exercise1-19 ) ใน SBCL แต่ใช้เวลาประมาณ 30 วินาทีจึงจะเสร็จ ฉันย้ายไปที่ C โดยใช้ GMP และส่งคืนผลลัพธ์ที่ถูกต้องในเวลาประมาณ 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

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

ฉันพยายามทำให้เป็นขนาน (ก่อนเพิ่มแคช) โดยใช้งานประจำและเริ่มใช้หน่วยความจำ 19 GiB: /
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 ชั่วโมงในการทำสองเทอม คอมพิวเตอร์ของฉันเป็นคนเส็งเคร็ง ฉันจะมีหมายเลขในไฟล์ข้อความในวันที่ 22 ตุลาคม 1.2 gigs เป็นเรื่องใหญ่ที่จะแบ่งปันในการเชื่อมต่อของฉัน


1
ฉันสับสนกับคำตอบของคุณ pseudocode? และยังมีเวลา? โพสต์รหัส! ภาษาไม่สำคัญ!
บูธโดย

นั้นและเอาท์พุทควรเท่านั้นที่จะ 4 ล้านหรือดังนั้นตัวเลข ...
Wug
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.