エクストリームフィボナッチ


47

このWebサイトでは、フィボナッチのチャレンジが10億回繰り返されているため、10億回のフィボナッチチャレンジで物事を盛り上げましょう!

あなたの課題は、できるだけ短いプログラムで、1,000,000,000番目のフィボナッチ数の最初の1000桁の10進数を出力することです。その後、オプションで、残りの数字を含むがこれに限定されない、選択した追加の出力が続く場合があります。

私は大会のことを使用していますfib 0 = 0fib 1 = 1

プログラムは、実行してその正当性を検証するのに十分な速度である必要があります。このために、最初の1000桁を以下に示します。

7952317874554683467829385196197148189255542185234398913453039937343246686182519370050999626136556779332482035723222451226291714456275648259499530612111301255499879639516053459789018700567439946844843034599802419924043753401950114830107234265037841426980398387360784284231996457340782784200767760907777703183185744656536253511502851715963351023990699232595471322670365506482435966586886048627159716916351448788527427435508113909167963907380398242848033980110276370544264285032744364781198451825462130529529633339813483105771370128111851128247136311414208318983802526907917787094802217750859685116363883374847428036737147882079956688807509158372249451437519320162582002000530798309887261257028201907509370554232931107084976854715833585623910450679449120011564762925649144509531904684984417002512086504020779012501356177874199605085558317190905395134468919443313026824813363234190494375599262553025466528838122639433600483849535070647711986769279568548796855207684897741771784375859496425384355879105799

Your program must be fast enough for you to run it and verify its correctness.メモリはどうですか?
スティーブン

5
@ guest44851は誰と言いますか?;)
user1502040

1
a+=b;b+=a;少なくともパフォーマンスについて考えているのであれば、明らかにしようとしていた場合は、ループ(おそらくJava BigIntegerを使用)が明らかな選択だと思います。再帰的な実装は、常に私にとって恐ろしく非効率的だと思われていました。
ピーターコーデス

2
膨大な数をネイティブにサポートしていない言語で見たいと思います!
BradC

1
@BradC:私もそう思っていました。開発、デバッグ、最適化、およびゴルフに約2日かかりましたが、今ではx86 32ビットマシンコードの回答の準備ができています(文字列への変換とwrite()システムコールの作成を含む106バイト)。私はパフォーマンス要件が好きです。
ピーターコーデス

回答:


34

Python 2 + sympy、72バイト

from sympy import*
n=sqrt(5)
print'7'+`((.5+n/2)**1e9/n).evalf(1e3)`[2:]

オンラインでお試しください!

Jeff
Degeのおかげで実質的に0の用語を削除して-10バイト-1バイト(Zacharýのおかげで1000-> 1e3)-Erik the Outgolferのおかげで
不要な変数を削除して
2バイト-ZacharýのおかげでPython 2に移行して2バイトThePirateBay
-11おかげで11バイトで-3バイト、notjaganのおかげstrでバックティックの交換で -3バイト

OPの未公開のhaskellソリューションに勝ちました!


@JonathanAllan from sympy import*;sqrtバイトを節約しないことに気づいたimport sympy;sympy.sqrt:)
HyperNeutrino

うわー、これは速いですが、これはどのように機能しますか?
Kritixiリソス

これはフィボナッチ数の指数近似を使用し、最初のe3桁のみが必要であるという詳細からの利益を使用すると考えます。あれは正しいですか?
ファビアンレーリング

@Fabian sympyはPythonのシンボリック数学パッケージであるため、少なくとも非常に大きな数値(この数値は十分な大きさではありません)までは丸め誤差の問題はありません。その後、最初の1e3桁を取得するために計算します。そうしないと、.evalf(1e3)部品を削除すると、非常に短い科学表記法が表示されます。
ハイパーニュートリノ

1
sympyはpythonの標準ライブラリの一部ではないため、キャラクターカウントにsympyのソースを含めない限り、この応答は有効ではないようです...または、コードのゴルフ規則を完全に誤解していますか?
-thegreatemu

28

Python 2、106バイト

a,b=0,1
for c in bin(10**9):
 a,b=2*a*b-a*a,a*a+b*b
 if'1'==c:a,b=b,a+b
 while a>>3340:a/=10;b/=10
print a

オンラインでお試しください!

ライブラリはなく、整数演算のみ。ほぼ瞬時に実行されます。

コアは、分割統治のアイデンティティです。

f(2*n)   = 2*f(n)*f(n+1) - f(n)^2
f(2*n+1) = f(n)^2 + f(n+1)^2

これにより(a,b) = (f(n),f(n+1))、doubleに更新できますn -> 2*n。getを取得したいのでn=10**9、これにはlog_2(10**9)=30反復だけが必要です。バイナリ展開の各桁ごとに繰り返し行うことで構築nします。の場合、2倍の値は1ステップのフィボナッチシフトで上にシフトされます。10**9n->2*n+ccc==12*n -> 2*n+1(a,b)=(b+a,b)

値をa,b管理しやすい状態に保つために、最初の1006数字のみを格納102**3340 ~ 1e1006ます。


:いいね!派手な既成のライブラリは使用しませんlol。:D
HyperNeutrino

私はもっ​​と楽しいが、ゴルフの再発が少ないことを発見したa,b,c=a*a+b*b,a*a-c*c,b*b+c*c
ニール

21

x86 32ビットマシンコード(Linuxシステムコールを使用):106 105バイト

changelog:オフバージョンの定数はFib(1G)の結果を変更しないため、高速バージョンで1バイトを保存しました。

または102は、(使用して18%より遅い(Skylakeマイクロアーキテクチャ上の)バージョンのバイトmov/ sub/ cmcの代わりにlea/ cmpで搬出及び包装を生成するために、内側ループに10**9代わりに2**32)。または、最内ループのキャリー処理に分岐を含む〜5.3x低速バージョンの場合は101バイト。(25.4%の分岐予測率を測定しました!)

または、先行ゼロが許可されている場合は104/101バイト。(出力の1桁をスキップするハードコードに1バイト余分に必要です。これは、Fib(10 ** 9)に必要なことです)。

残念ながら、TIOのNASMモード-felf32はコンパイラフラグで無視されるようです。 とにかく、コメントに実験的なアイデアの混乱をすべて含む、完全なソースコードとのリンクがあります。

これは完全なプログラムです。Fib(10 ** 9)の最初の1000桁に続いて、余分な数字(最後の数桁が間違っている)に続いて、ごみバイト(改行を含まない)が出力されます。ガベージのほとんどは非ASCIIなので、を介してパイプすることもできますcat -vkonsoleただし、端末エミュレーター(KDE )は壊れません。「ガベージバイト」はFib(999999999)を格納しています。私はすでに-1024レジスタを持っているので、適切なサイズよりも1024バイトを印刷する方が安価でした。

マシンコード(静的実行可能ファイルのテキストセグメントのサイズ)だけをカウントしています。ELF実行可能ファイルにするための綿毛はカウントしていません。(非常に小さなELF実行可能ファイルが可能ですが、それを気にしたくありませんでした)。BSSの代わりにスタックメモリを使用する方が短いことがわかったので、メタデータに依存しないため、バイナリ内の他のものを数えないことを正当化できます。(通常の方法でストリップされた静的バイナリを生成すると、340バイトのELFが実行可能になります。)

Cから呼び出すことができるこのコードから関数を作成できます。スタックポインター(MMXレジスタ内など)とその他のオーバーヘッドを保存/復元するには数バイトかかりますが、文字列を返すことでバイトを節約することもできます。write(1,buf,len)システムコールを行う代わりに、メモリ内で。マシンコードでのゴルフは、ネイティブ拡張精度を使用せずに他の言語で回答を投稿した人もいないため、ここで多少の余裕があると思いますが、この機能バージョンは全体を再ゴルフすることなく、120バイト未満である必要があります事。


アルゴリズム:

ブルートフォースa+=b; swap(a,b)、必要に応じて切り捨てて、先頭の1017桁以上の10進数のみを保持します。私のコンピューターでは1分13秒(または322.47億クロックサイクル±0.05%)で実行されます(さらに、コードサイズの追加バイト数で数%速くなるか、ループ展開からのコードサイズがはるかに大きい62 秒まで短縮されます。巧妙な数学、より少ないオーバーヘッドで同じ仕事をするだけです)。それは私のコンピューター(4.4GHz Skylake i7-6700k)で12min35sで実行される@AndersKaseorgのPython実装に基づいています。どちらのバージョンにもL1Dキャッシュミスがないため、DDR4-2666は関係ありません。

Pythonとは異なり、拡張精度の数値は、小数点以下を切り捨てられる形式で保存します。32ビット整数ごとに9桁の10進数のグループを格納するため、ポインターオフセットは下位9桁を破棄します。これは事実上10億の基数であり、10の累乗です(このチャレンジには10億のフィボナッチ数が必要であることはまったく偶然ですが、2つの独立した定数に対して数バイト節約できます)。

GMPの用語に従って、拡張精度の数値の各32ビットチャンクは「リム」と呼ばれます。追加中のキャリーアウトは、1e9と比較して手動で生成する必要がありADCますが、次の肢の通常の指示への入力として通常使用されます。([0..999999999]2 ^ 32〜= 4.295e9ではなく、手動で範囲にラップする必要があります。比較のキャリーアウト結果を使用して、lea+ cmovでブランチレスにこれを行います。)

最後のリムがゼロ以外のキャリーアウトを生成する場合、外側のループの次の2回の繰り返しは、通常よりも1リム高い値から読み取りますが、同じ場所に書き込みます。これはmemcpy(a, a+4, 114*4)、1つ右にシフトするのと似ていますが、次の2つの加算ループの一部として行われます。これは、18回の反復ごとに発生します。


サイズ節約とパフォーマンスのためのハック:

  • 私がそれを知っているときのlea ebx, [eax-4 + 1]代わりにmov ebx, 1、のような通常のものeax=4。またloopLOOPの遅さがわずかな影響しか与えない場所で使用します。

  • adc内側のループのバッファーの先頭に書き込みながら、読み取り元のポインターをオフセットすることにより、自由に1リムずつ切り捨てます。から読み取り[edi+edx]、書き込み[edi]ます。したがって、宛先の読み取り/書き込みオフセットを取得edx=0または4取得できます。これを2回連続して繰り返し、最初に両方をオフセットし、次にdstのみをオフセットする必要があります。2番目のケースesp&4は、バッファの先頭へのポインタをリセットする前に確認することで検出されます(&= -1024バッファが整列しているため、を使用します)。コード内のコメントを参照してください。

  • Linuxプロセス起動環境(静的実行可能ファイル用)はほとんどのレジスタをゼロにし、esp/の下のスタックメモリrspはゼロになります。私のプログラムはこれを利用しています。これの呼び出し可能関数バージョン(未割り当てスタックがダーティになる可能性がある場合)では、ゼロ化されたメモリにBSSを使用できます(ポインターをセットアップするためにさらに4バイトのコストがかかります)。ゼロ化にedxは2バイトかかります。x86-64 System V ABIはこれらのいずれも保証しませんが、Linuxの実装ではゼロになります(カーネルからの情報漏えいを防ぐため)。動的にリンクされたプロセスで/lib/ld.soは、の前に実行_startされ、レジスタをゼロ以外のままにします(おそらくスタックポインタの下のメモリ内のゴミ)。

  • 私はキープ-1024ebxのループの使用外のため。blゼロで終わる内部ループのカウンターとして使用します(これはの下位バイトで-1024あるため、ループ外で使用するために定数を復元します)。Intel Haswell以降では、low8レジスタの部分レジスタマージペナルティはありません(実際個別に名前を変更することさえしません)。したがって、AMDのように、フルレジスタに依存しています(ここでは問題ありません)。ただし、Nehalem以前では、マージ時に部分的なレジスタストールがあるため、これは恐ろしいことです。部分的なregをxor作成し-zeroingまたはa なしで完全なregを読み取る他の場所がありますmovzx、通常、以前のコードが上位バイトをゼロにしたことを知っているためです。AMDおよびIntel SnBファミリでは問題ありませんが、Intel pre-Sandybridgeでは低速です。

    私は1024stdout(sub edx, ebx)に書き込むためのバイト数として使用しmov edx, 1000ます。そのため、より多くのバイトが必要になるため、プログラムはフィボナッチ桁の後にゴミバイトを出力します。

  • (使用されません)adc ebx,ebxEBX = 0でEBX = CFを取得し、1バイトを節約しますsetc bl

  • dec/ ループjnz内では、Intel Sandybridge以降でフラグを読み取るadcときに部分フラグストールを引き起こすことなくCFを保持しadcます。 初期のCPUでは悪いですが、Skylakeでは無料です。または最悪の場合、余分なuop。

  • 以下のメモリをesp巨大なレッドゾーンとして使用します。これは完全なLinuxプログラムであるため、シグナルハンドラーをインストールしなかったこと、およびユーザー空間のスタックメモリを非同期に上書きするものはないことを知っています。これは、他のOSでは当てはまらない場合があります。

  • 活用して、スタック・エンジン使用してUOP問題の帯域幅を節約するためにpop eax(1つのUOP +時折スタック同期UOP)の代わりにlodsd(によるIVBのハスウェル/ Skylakeマイクロアーキテクチャ、3の2つのuopおよびそれ以前のAgner霧の命令テーブル))。IIRCでは、実行時間が約83秒から73に短縮されました。src とdstバッファ間のオフセットを保持する場所のmovように、インデックス付きアドレッシングモードでa を使用してもおそらく同じ速度が得られます。(内部ループの外側のコードはより複雑になり、フィボナッチ反復のsrcとdstの交換の一部としてオフセットレジスタを無効にする必要があります。)詳細については、以下の「パフォーマンス」セクションを参照してください。mov eax, [edi+ebp]ebp

  • をメモリ内のどこかにstc保存する代わりに、最初の反復にキャリーイン(1バイト)を与えることでシーケンスを開始します1。コメントに文書化された他の多くの問題固有のもの。

で生成されたNASMリスト(マシンコード+ソース)nasm -felf32 fibonacci-1G.asm -l /dev/stdout | cut -b -28,$((28+12))- | sed 's/^/ /'。(それからコメントされたもののいくつかのブロックを手で取り除いたので、行番号にはギャップがあります。)YASMまたはNASMにフィードできるように先頭の列を取り除くには、を使用しますcut -b 27- <fibonacci-1G.lst > fibonacci-1G.asm

  1          machine      global _start
  2          code         _start:
  3 address

  4 00000000 B900CA9A3B       mov    ecx, 1000000000       ; Fib(ecx) loop counter
  5                       ;    lea    ebp, [ecx-1]          ;  base-1 in the base(pointer) register ;)
  6 00000005 89CD             mov    ebp, ecx    ; not wrapping on limb==1000000000 doesn't change the result.
  7                                              ; It's either self-correcting after the next add, or shifted out the bottom faster than Fib() grows.
  8                       
 42                       
 43                       ;    mov    esp, buf1
 44                       
 45                       ;    mov    esi, buf1   ; ungolfed: static buffers instead of the stack
 46                       ;    mov    edi, buf2

 47 00000007 BB00FCFFFF       mov    ebx, -1024
 48 0000000C 21DC             and    esp, ebx    ; alignment necessary for convenient pointer-reset
 49                       ;    sar    ebx, 1
 50 0000000E 01DC             add    esp, ebx     ; lea    edi, [esp + ebx].  Can't skip this: ASLR or large environment can put ESP near the bottom of a 1024-byte block to start with
 51 00000010 8D3C1C           lea    edi, [esp + ebx*1]
 52                           ;xchg   esp, edi   ; This is slightly faster.  IDK why.
 53                       
 54                           ; It's ok for EDI to be below ESP by multiple 4k pages.  On Linux, IIRC the main stack automatically extends up to ulimit -s, even if you haven't adjusted ESP.  (Earlier I used -4096 instead of -1024)
 55                           ; After an even number of swaps, EDI will be pointing to the lower-addressed buffer
 56                           ; This allows a small buffer size without having the string step on the number.
 57
 58                       ; registers that are zero at process startup, which we depend on:
 59                       ;    xor   edx, edx
 60                       ;;  we also depend on memory far below initial ESP being zeroed.
 61
 62 00000013 F9               stc    ; starting conditions: both buffers zeroed, but carry-in = 1
 63                       ; starting Fib(0,1)->0,1,1,2,3 vs. Fib(1,0)->1,0,1,1,2 starting "backwards" puts us 1 count behind
 66
 67                       ;;; register usage:
 68                       ;;; eax, esi: scratch for the adc inner loop, and outer loop
 69                       ;;; ebx: -1024.  Low byte is used as the inner-loop limb counter (ending at zero, restoring the low byte of -1024)
 70                       ;;; ecx: outer-loop Fibonacci iteration counter
 71                       ;;; edx: dst read-write offset (for "right shifting" to discard the least-significant limb)
 72                       ;;; edi: dst pointer
 73                       ;;; esp: src pointer
 74                       ;;; ebp: base-1 = 999999999.  Actually still happens to work with ebp=1000000000.
 75
 76                       .fibonacci:
 77                       limbcount equ 114             ; 112 = 1006 decimal digits / 9 digits per limb.  Not enough for 1000 correct digits, but 114 is.
 78                                                     ; 113 would be enough, but we depend on limbcount being even to avoid a sub
 79 00000014 B372             mov    bl, limbcount
 80                       .digits_add:
 81                           ;lodsd                       ; Skylake: 2 uops.  Or  pop rax  with rsp instead of rsi
 82                       ;    mov    eax, [esp]
 83                       ;    lea    esp, [esp+4]   ; adjust ESP without affecting CF.  Alternative, load relative to edi and negate an offset?  Or add esp,4 after adc before cmp
 84 00000016 58               pop    eax
 85 00000017 130417           adc    eax, [edi + edx*1]    ; read from a potentially-offset location (but still store to the front)
 86                        ;; jz .out   ;; Nope, a zero digit in the result doesn't mean the end!  (Although it might in base 10**9 for this problem)
 87
 88                       %if 0   ;; slower version
                          ;; could be even smaller (and 5.3x slower) with a branch on CF: 25% mispredict rate
 89                           mov  esi, eax
 90                           sub  eax, ebp  ; 1000000000 ; sets CF opposite what we need for next iteration
 91                           cmovc eax, esi
 92                           cmc                         ; 1 extra cycle of latency for the loop-carried dependency. 38,075Mc for 100M iters (with stosd).
 93                                                       ; not much worse: the 2c version bottlenecks on the front-end bottleneck
 94                       %else   ;; faster version
 95 0000001A 8DB0003665C4     lea    esi, [eax - 1000000000]
 96 00000020 39C5             cmp    ebp, eax                ; sets CF when (base-1) < eax.  i.e. when eax>=base
 97 00000022 0F42C6           cmovc  eax, esi                ; eax %= base, keeping it in the [0..base) range
 98                       %endif
 99                       
100                       %if 1
101 00000025 AB               stosd                          ; Skylake: 3 uops.  Like add + non-micro-fused store.  32,909Mcycles for 100M iters (with lea/cmp, not sub/cmc)
102                       %else
103                         mov    [edi], eax                ; 31,954Mcycles for 100M iters: faster than STOSD
104                         lea   edi, [edi+4]               ; Replacing this with ADD EDI,4 before the CMP is much slower: 35,083Mcycles for 100M iters
105                       %endif
106                       
107 00000026 FECB             dec    bl                      ; preserves CF.  The resulting partial-flag merge on ADC would be slow on pre-SnB CPUs
108 00000028 75EC             jnz .digits_add
109                           ; bl=0, ebx=-1024
110                           ; esi has its high bit set opposite to CF
111                       .end_innerloop:
112                           ;; after a non-zero carry-out (CF=1): right-shift both buffers by 1 limb, over the course of the next two iterations
113                           ;; next iteration with r8 = 1 and rsi+=4:  read offset from both, write normal.  ends with CF=0
114                           ;; following iter with r8 = 1 and rsi+=0:  read offset from dest, write normal.  ends with CF=0
115                           ;; following iter with r8 = 0 and rsi+=0:  i.e. back to normal, until next carry-out (possible a few iters later)
116                       
117                           ;; rdi = bufX + 4*limbcount
118                           ;; rsi = bufY + 4*limbcount + 4*carry_last_time
119                       
120                       ;    setc   [rdi]
123 0000002A 0F92C2           setc   dl
124 0000002D 8917             mov    [edi], edx ; store the carry-out into an extra limb beyond limbcount
125 0000002F C1E202           shl    edx, 2

139                           ; keep -1024 in ebx.  Using bl for the limb counter leaves bl zero here, so it's back to -1024 (or -2048 or whatever)
142 00000032 89E0             mov    eax, esp   ; test/setnz could work, but only saves a byte if we can somehow avoid the  or dl,al
143 00000034 2404             and    al, 4      ; only works if limbcount is even, otherwise we'd need to subtract limbcount first.

148 00000036 87FC             xchg   edi, esp   ; Fibonacci: dst and src swap
149 00000038 21DC             and    esp, ebx  ; -1024  ; revert to start of buffer, regardless of offset
150 0000003A 21DF             and    edi, ebx  ; -1024
151                       
152 0000003C 01D4             add    esp, edx             ; read offset in src

155                           ;; after adjusting src, so this only affects read-offset in the dst, not src.
156 0000003E 08C2             or    dl, al              ; also set r8d if we had a source offset last time, to handle the 2nd buffer
157                           ;; clears CF for next iter

165 00000040 E2D2             loop .fibonacci  ; Maybe 0.01% slower than dec/jnz overall

169                       to_string:

175                       stringdigits equ 9*limbcount  ; + 18
176                       ;;; edi and esp are pointing to the start of buffers, esp to the one most recently written
177                       ;;;  edi = esp +/- 2048, which is far enough away even in the worst case where they're growing towards each other
178                       ;;;  update: only 1024 apart, so this only works for even iteration-counts, to prevent overlap

180                           ; ecx = 0 from the end of the fib loop
181                           ;and   ebp, 10     ; works because the low byte of 999999999 is 0xff
182 00000042 8D690A           lea    ebp, [ecx+10]         ;mov    ebp, 10
183 00000045 B172             mov    cl, (stringdigits+8)/9
184                       .toascii:  ; slow but only used once, so we don't need a multiplicative inverse to speed up div by 10
185                           ;add   eax, [rsi]    ; eax has the carry from last limb:  0..3  (base 4 * 10**9)
186 00000047 58               pop    eax                  ; lodsd
187 00000048 B309             mov    bl, 9
188                       .toascii_digit:
189 0000004A 99               cdq                         ; edx=0 because eax can't have the high bit set
190 0000004B F7F5             div    ebp                  ; edx=remainder = low digit = 0..9.  eax/=10

197 0000004D 80C230           add    dl, '0'
198                                              ; stosb  ; clobber [rdi], then  inc rdi
199 00000050 4F               dec    edi         ; store digits in MSD-first printing order, working backwards from the end of the string
200 00000051 8817             mov    [edi], dl
201                       
202 00000053 FECB             dec    bl
203 00000055 75F3             jnz  .toascii_digit
204                       
205 00000057 E2EE             loop .toascii
206                       
207                           ; Upper bytes of eax=0 here.  Also AL I think, but that isn't useful
208                           ; ebx = -1024
209 00000059 29DA             sub  edx, ebx   ; edx = 1024 + 0..9 (leading digit).  +0 in the Fib(10**9) case
210                       
211 0000005B B004             mov   al, 4                 ; SYS_write
212 0000005D 8D58FD           lea  ebx, [eax-4 + 1]       ; fd=1
213                           ;mov  ecx, edi               ; buf
214 00000060 8D4F01           lea  ecx, [edi+1]           ; Hard-code for Fib(10**9), which has one leading zero in the highest limb.
215                       ;    shr  edx, 1 ;    for use with edx=2048
216                       ;    mov  edx, 100
217                       ;    mov byte  [ecx+edx-1], 0xa;'\n'  ; count+=1 for newline
218 00000063 CD80             int  0x80                   ; write(1, buf+1, 1024)
219                       
220 00000065 89D8             mov  eax, ebx ; SYS_exit=1
221 00000067 CD80             int  0x80     ; exit(ebx=1)
222                       
  # next byte is 0x69, so size = 0x69 = 105 bytes

おそらくこれからさらにバイトをゴルフする余地がありますが、私はすでに2日間でこれに少なくとも12時間を費やしました。 速度が十分に速いとはいえ、速度を犠牲にしたくありませんし、速度を犠牲にするほど小さくする余地があります。投稿の理由の一部は、ブルートフォースのasmバージョンを作成できる速さを示すことです。本当に最小サイズにしたいが、おそらく10倍遅い(たとえば、1バイトあたり1桁)ことを望む人は、これを出発点として自由にコピーしてください。

結果の実行可能ファイル(からyasm -felf32 -Worphan-labels -gdwarf2 fibonacci-1G.asm && ld -melf_i386 -o fibonacci-1G fibonacci-1G.o)は340B(削除)です。

size fibonacci-1G
 text    data     bss     dec     hex filename
  105       0       0     105      69 fibonacci-1G

性能

内部adcループは、Skylakeで10個の融合ドメインuop(128バイトごとに+1スタック同期uop)であるため、最適なフロントエンドスループットでSkylakeで〜2.5サイクルごとに1回発行できます(スタック同期uopを無視) 。adc-> cmp->次の反復のadcループキャリー依存チェーンのクリティカルパス遅延は2サイクルであるため、ボトルネックは反復ごとに〜2.5サイクルのフロントエンドの問題の制限である必要があります。

adc eax, [edi + edx]実行ポート用の2つの非融合ドメインuop:load + ALU。デコーダーではマイクロヒューズ(1 つの融合ドメインuop)が発生しますが、Haswell / Skylakeでもインデックス付きアドレッシングモードのため、発行段階では2つの融合ドメインuopにラミネートされません。私はそれがそうであるように、マイクロ融合したままになると思ったがadd eax, [edi + edx]、多分、インデックス付きアドレス指定モードを維持することは、すでに3つの入力(フラグ、メモリ、および宛先)を持っているuopに対しては機能しない。書いたとき、パフォーマンスにマイナス面はないと思っていましたが、間違っていました。この切り捨ての処理方法edxは、0または4 であるかどうかに関係なく、毎回内側のループを遅くします。

オフセットediを使用edxしてストアを調整することにより、dstの読み取り/書き込みオフセットを処理する方が高速です。だからadc eax, [edi]/ ... / mov [edi+edx], eax/のlea edi, [edi+4]代わりにstosd。Haswell以降では、インデックス化されたストアをマイクロ融合状態に保つことができます。(Sandybridge / IvBもそれをラミネート解除します。)

Intel Haswell以前ではadccmovcそれぞれ2 uopsであり、レイテンシは2cです。(adc eax, [edi+edx]Haswellではまだラミネートされておらず、3つの融合ドメインuopとして問題があります)。Broadwellマイクロアーキテクチャ以降は3入力だけFMA(ハスウェル)以上のためのuop、作ることを可能adccmovc彼らは長い間、AMDにされているように、(カップル他のもの)のシングルUOPの指示を。(これが、AMDが拡張精度GMPベンチマークで長い間うまく行っている理由の1つです。)とにかく、Haswellの内部ループは12 uop(時々スタック同期uop +1)で、フロントエンドのボトルネックは1スタック同期uopを無視して、最適なケースを繰り返します。

ループ内でpopバランシングなしで使用pushすると、ループはLSD(ループストリームディテクター)から実行できなくなり、uopキャッシュからIDQに毎回再読み取りする必要があります。9または10 uopループはサイクルごとに4 uopsで最適に発行されないため Skylakeではそれが良いことです。これはおそらく、交換理由の一部であるlodsdpopそんなに助けました。(スタック同期 uopを挿入する余地を残さないため、LSDはuopをロックダウンできません。)(BTW、マイクロコードの更新により、SkylakeとSkylake-XでLSDが完全に無効になり、エラッタが修正されます。上記のアップデートを取得する前に。)

Haswellでプロファイルを作成したところ、(メモリではなくL1Dキャッシュのみを使用しているため、CPU周波数に関係なく)3813億1,000万クロックサイクルで実行されることがわかりました。フロントエンドの問題のスループットは、Skylakeの3.70に対して、クロックあたり3.72融合ドメインuopsでした。(もちろん、Haswellでは2 uopであるためadc、サイクルあたりの命令は2.87から2.42に減少しましたcmov。)

pushスタックシンクuopを毎回トリガーするstosdため、交換することはおそらくあまり役​​に立ちませんadc [esp + edx]。また、1バイトかかるstdためlodsd、逆方向に進みます。(mov [edi], eax/ lea edi, [edi+4]を置き換えるstosdと勝利となり、1億反復の32,909Mサイクルから1億反復の31,954Mサイクルになります。3 stosdつのuopとしてデコードされ、store-address / store-data uopsはマイクロ融合されないため、push+ stack-sync uopsはまだより高速かもしれませんstosd

Skylakeの高速105Bバージョンでは、114肢の1G反復で〜322.47億サイクルの実際のパフォーマンスは、内側ループの反復あたり2.824サイクルになります。(ocperf.py以下の出力を参照してください)。それは静的解析から予測したよりも遅いですが、外側のループとスタック同期uopのオーバーヘッドを無視していました。

Perf は、内側のループが外側のループごとに1回(最後の反復で、それが実行されない場合)誤予測することbranchesbranch-misses示します。それは余分な時間の一部も占めています。


Iは、最も内側のループを使用して、クリティカル・パスのために3サイクルの待ち時間を有することにより、コードサイズを節約できるmov esi,eax/ sub eax,ebp/ cmovc eax, esi/のcmc代わりに、(2 + 2 + 3 + 1 = 8B)lea esi, [eax - 1000000000]/ cmp ebp,eax/ cmovc(6 + 2 + 3 = 11B )。cmov/ stosdクリティカルパスから外れています。(の増分編集uopはstosdストアとは別に実行できるため、各反復は短い依存関係チェーンから分岐します。)以前はebp init命令をからlea ebp, [ecx-1]に変更して別の1Bを保存mov ebp,eaxしていましたが、間違っていることがわかりましたebp結果は変わりませんでした。これにより、四肢はラップしてキャリーを生成する代わりに== 1000000000になりますが、このエラーはFib()が成長するよりも遅く伝播するため、最終結果の先頭の1k桁は変更されません。また、手足にはオーバーフローせずに保持する余地があるため、追加するだけでエラーが修正されると思います。1G + 1Gでさえ32ビット整数をオーバーフローさせないため、最終的には上に浸透するか、切り捨てられます。

3cレイテンシバージョンは1 uop追加であるため、フロントエンドはSkylakeで2.75cサイクルごとに1回発行できますが、バックエンドが実行できるよりもわずかに高速です。(Haswellでは、まだとを使用adcしているため、合計で13 uopsになりますcmov。フロントエンドのボトルネックは、iterあたり3.25cです)。

実際には、Skylakeでは1.18倍遅くなります(手足あたり3.34サイクル)。スタック同期なしで内部ループを見るだけでフロントエンドのボトルネックをレイテンシボトルネックに置き換えると予測した3 / 2.5 = 1.2ではなく、おっと。スタック同期uopは高速バージョン(レイテンシの代わりにフロントエンドでボトルネック)を損なうだけなので、それを説明するのにそれほど時間はかかりません。例:3 / 2.54 = 1.18。

もう1つの要因は、3cレイテンシバージョンが、クリティカルパスの実行中に内部ループを離れる際の予測ミスを検出する可能性があることです(フロントエンドがバックエンドよりも先に進み、アウトオブオーダー実行がループを実行できるためです-カウンターuops)、したがって、効果的な予測ミスのペナルティは低くなります。これらのフロントエンドサイクルを失うと、バックエンドが追いつきます。

そうでない場合cmcは、carry_out-> edxおよびespオフセットをブランチレスで処理する代わりに、外側のループでブランチを使用することで、3c バージョンを高速化できます。分岐予測+データ依存関係ではなく制御依存関係の投機的実行adcにより、前の内部ループからのuopsがまだ実行されている間に次の反復がループの実行を開始する可能性があります。ブランチレスバージョンでは、内部ループのロードアドレスadcは、最後の肢の最後からCFへのデータ依存関係を持ちます。

2cレイテンシのインナーループバージョンはフロントエンドでボトルネックになるため、バックエンドはほとんど追いつきません。外側のループコードのレイテンシが高い場合、フロントエンドは、内側のループの次の反復からuopを発行して先に進む可能性があります。(ただし、この場合、アウターループのものにはILPが多くあり、高遅延のものはありません。したがって、バックエンドは、アウトオブオーダースケジューラでuopsを噛み始めたときに追いつくことができません。入力が準備完了になります)。

### Output from a profiled run
$ asm-link -m32 fibonacci-1G.asm && (size fibonacci-1G; echo disas fibonacci-1G) && ocperf.py stat -etask-clock,context-switches:u,cpu-migrations:u,page-faults:u,cycles,instructions,uops_issued.any,uops_executed.thread,uops_executed.stall_cycles -r4  ./fibonacci-1G
+ yasm -felf32 -Worphan-labels -gdwarf2 fibonacci-1G.asm
+ ld -melf_i386 -o fibonacci-1G fibonacci-1G.o
   text    data     bss     dec     hex filename
    106       0       0     106      6a fibonacci-1G
disas fibonacci-1G
perf stat -etask-clock,context-switches:u,cpu-migrations:u,page-faults:u,cycles,instructions,cpu/event=0xe,umask=0x1,name=uops_issued_any/,cpu/event=0xb1,umask=0x1,name=uops_executed_thread/,cpu/event=0xb1,umask=0x1,inv=1,cmask=1,name=uops_executed_stall_cycles/ -r4 ./fibonacci-1G
79523178745546834678293851961971481892555421852343989134530399373432466861825193700509996261365567793324820357232224512262917144562756482594995306121113012554998796395160534597890187005674399468448430345998024199240437534019501148301072342650378414269803983873607842842319964573407827842007677609077777031831857446565362535115028517159633510239906992325954713226703655064824359665868860486271597169163514487885274274355081139091679639073803982428480339801102763705442642850327443647811984518254621305295296333398134831057713701281118511282471363114142083189838025269079177870948022177508596851163638833748474280367371478820799566888075091583722494514375193201625820020005307983098872612570282019075093705542329311070849768547158335856239104506794491200115647629256491445095319046849844170025120865040207790125013561778741996050855583171909053951344689194433130268248133632341904943755992625530254665288381226394336004838495350706477119867692795685487968552076848977417717843758594964253843558791057997424878788358402439890396,�X\�;3�I;ro~.�'��R!q��%��X'B ��      8w��▒Ǫ�
 ... repeated 3 more times, for the 3 more runs we're averaging over
  Note the trailing garbage after the trailing digits.

 Performance counter stats for './fibonacci-1G' (4 runs):

      73438.538349      task-clock:u (msec)       #    1.000 CPUs utilized            ( +-  0.05% )
                 0      context-switches:u        #    0.000 K/sec                  
                 0      cpu-migrations:u          #    0.000 K/sec                  
                 2      page-faults:u             #    0.000 K/sec                    ( +- 11.55% )
   322,467,902,120      cycles:u                  #    4.391 GHz                      ( +-  0.05% )
   924,000,029,608      instructions:u            #    2.87  insn per cycle           ( +-  0.00% )
 1,191,553,612,474      uops_issued_any:u         # 16225.181 M/sec                   ( +-  0.00% )
 1,173,953,974,712      uops_executed_thread:u    # 15985.530 M/sec                   ( +-  0.00% )
     6,011,337,533      uops_executed_stall_cycles:u #   81.855 M/sec                    ( +-  1.27% )

      73.436831004 seconds time elapsed                                          ( +-  0.05% )

( +- x %)は、そのカウントの4回の実行に対する標準偏差です。興味深いことに、このようなラウンド数の命令を実行します。その9,240億は偶然ではありません。外側のループは合計924命令を実行していると思います。

uops_issued融合ドメインカウント(フロントエンドの問題の帯域幅に関連)でuops_executedあり、非融合ドメインカウント(実行ポートに送信されたuopの数)です。マイクロフュージョンは、2つのアンフューズドドメインuopを1つのフューズドドメインuopにパックしますが、mov-eliminationは、一部のフューズドドメインuopsが実行ポートを必要としないことを意味します。uopと融合ドメインと非融合ドメインのカウントについて詳しくは、リンクされた質問をご覧ください。(Agner Fogの手順表とuarchガイド、およびSO x86タグwikiの他の便利なリンクも参照してください)。

さまざまなことを測定する別の実行から:同じ2つの456Bバッファーの読み取り/書き込みで予想されるように、L1Dキャッシュミスはまったく重要ではありません。内側のループ分岐は、外側のループごとに1回予測を誤ります(ループから抜けない場合)。(コンピューターが完全にアイドル状態ではなかったため、合計時間が長くなりました。おそらく、他の論理コアがいくつかの時間アクティブであり、割り込みに多くの時間が費やされました(ユーザー空間で測定された周波数が4.400GHzをはるかに下回っていたため)。または、複数のコアがより頻繁にアクティブになり、最大ターボが低下しましたcpu_clk_unhalted.one_thread_active。HTの競合が問題になるかどうかを追跡しませんでした。)

     ### Another run of the same 105/106B "main" version to check other perf counters
      74510.119941      task-clock:u (msec)       #    1.000 CPUs utilized          
                 0      context-switches:u        #    0.000 K/sec                  
                 0      cpu-migrations:u          #    0.000 K/sec                  
                 2      page-faults:u             #    0.000 K/sec                  
   324,455,912,026      cycles:u                  #    4.355 GHz                    
   924,000,036,632      instructions:u            #    2.85  insn per cycle         
   228,005,015,542      L1-dcache-loads:u         # 3069.535 M/sec
           277,081      L1-dcache-load-misses:u   #    0.00% of all L1-dcache hits
                 0      ld_blocks_partial_address_alias:u #    0.000 K/sec                  
   115,000,030,234      branches:u                # 1543.415 M/sec                  
     1,000,017,804      branch-misses:u           #    0.87% of all branches        

私のコードは、Ryzenでより少ないサイクルで実行される可能性があり、サイクルあたり5 uop(または、RyzenのAVX 256bなどの2 uop命令の場合は6 uop)を発行できます。フロントエンドがstosdRyzenで3 uop(Intelと同じ)で何をするのかわかりません。内部ループの他の命令は、Skylakeおよびすべての単一uopと同じレイテンシーだと思います。(adc eax, [edi+edx]Skylakeよりも有利なを含む)。


これはおそらく大幅に小さくなる可能性がありますが、バイトごとに1桁の10進数として数値を保存すると、9倍遅くなる可能性があります。キャリーアウトの生成cmpと調整cmovは同じように機能しますが、作業の1/9を行います。1バイトあたり2桁の10進数(base-100、低速のDAA 4ビットBCDではない)も機能し、div r8/ add ax, 0x3030は0-99バイトを印刷順序で2つのASCII数字に変換します。ただし、バイトごとに1桁は必要ありませんdiv。ループして0x30を追加するだけです。バイトを印刷順に保存すると、2番目のループが非常に簡単になります。


64ビット整数ごとに18桁または19桁の10進数を使用すると(64ビットモードで)約2倍の速度で実行されますが、すべてのREXプレフィックスと64ビット定数のコードサイズが大きくなります。64ビットモードの32ビットリムでは、のpop eax代わりに使用できませんlodsd。8番目のレジスタとして使用する代わりにesp、非ポインタースクラッチレジスタとして使用することで、REXプレフィックスを回避できました(esiand の使用法を交換しますespr8d

呼び出し可能関数バージョンを作成する場合、64ビットに変換して使用r8dする方が、保存/復元するよりも安くなる場合がありますrsp。64ビットでは、1バイトdec r32エンコーディングを使用できません(REXプレフィックスであるため)。しかし、ほとんどの場合、dec bl2バイトを使用することになりました。(の上位バイトに定数があり、ebxそれを内部ループの外側でのみ使用しているため、定数の下位バイトがであるため機能します0x00。)


高性能バージョン

最大のパフォーマンス(コードゴルフではない)を得るには、最大22回の反復を実行するように内側のループを展開する必要があります。これは、分岐予測子がうまく機能するために十分に短いパターンです。私の実験ではmov cl, 22.inner: dec cl/jnz .innerループの予測ミスはほとんどありませんが(0.05%など、内部ループの完全な実行ごとに1未満)、mov cl,23内部ループごとに0.35から0.6回の予測ミスがあります。 46特に悪いのは、内部ループごとに〜1.28回(100Mの外部ループの繰り返しで128M回)の予測ミスです。 114フィボナッチループの一部として見つけたのと同じように、内側のループごとに1回だけ予測ミスをしました。

私は好奇心got盛になり、それを試してみました%rep 6(内側のループを114で均等に分割するため)。これにより、分岐ミスがほとんどなくなりました。edxネガを作成し、mov店舗のオフセットとして使用したので、adc eax,[edi]マイクロフューズのままにすることができました。(そして、私は避けることができましたstosd)。をlea更新ediして%repブロックから更新したため、6つのストアごとに1つのポインター更新のみを実行します。

また、外側のループ内のすべての部分レジスタを削除しましたが、それは重要ではないと思います。最終的なADCに依存しない外側のループの終わりにCFを置くと少し助けになったので、内側のループuopの一部を開始できます。アウターループコードはおそらくもう少し最適化できます。これneg edxは、xchgたった2つのmov命令に置き換えた後(私はまだ1つを持っているため)、8ビットをドロップするとともにdepチェーンを再配置した後、最後にやったことだったからですものを登録します。

これは、フィボナッチループのNASMソースです。これは、元のバージョンのそのセクションのドロップイン置換です。

  ;;;; Main loop, optimized for performance, not code-size
%assign unrollfac 6
    mov    bl, limbcount/unrollfac  ; and at the end of the outer loop
    align 32
.fibonacci:
limbcount equ 114             ; 112 = 1006 decimal digits / 9 digits per limb.  Not enough for 1000 correct digits, but 114 is.
                              ; 113 would be enough, but we depend on limbcount being even to avoid a sub
;    align 8
.digits_add:

%assign i 0
%rep unrollfac
    ;lodsd                       ; Skylake: 2 uops.  Or  pop rax  with rsp instead of rsi
;    mov    eax, [esp]
;    lea    esp, [esp+4]   ; adjust ESP without affecting CF.  Alternative, load relative to edi and negate an offset?  Or add esp,4 after adc before cmp
    pop    eax
    adc    eax, [edi+i*4]    ; read from a potentially-offset location (but still store to the front)
 ;; jz .out   ;; Nope, a zero digit in the result doesn't mean the end!  (Although it might in base 10**9 for this problem)

    lea    esi, [eax - 1000000000]
    cmp    ebp, eax                ; sets CF when (base-1) < eax.  i.e. when eax>=base
    cmovc  eax, esi                ; eax %= base, keeping it in the [0..base) range
%if 0
    stosd
%else
  mov    [edi+i*4+edx], eax
%endif
%assign i i+1
%endrep
  lea   edi, [edi+4*unrollfac]

    dec    bl                      ; preserves CF.  The resulting partial-flag merge on ADC would be slow on pre-SnB CPUs
    jnz .digits_add
    ; bl=0, ebx=-1024
    ; esi has its high bit set opposite to CF
.end_innerloop:
    ;; after a non-zero carry-out (CF=1): right-shift both buffers by 1 limb, over the course of the next two iterations
    ;; next iteration with r8 = 1 and rsi+=4:  read offset from both, write normal.  ends with CF=0
    ;; following iter with r8 = 1 and rsi+=0:  read offset from dest, write normal.  ends with CF=0
    ;; following iter with r8 = 0 and rsi+=0:  i.e. back to normal, until next carry-out (possible a few iters later)

    ;; rdi = bufX + 4*limbcount
    ;; rsi = bufY + 4*limbcount + 4*carry_last_time

;    setc   [rdi]
;    mov    dl, dh               ; edx=0.  2c latency on SKL, but DH has been ready for a long time
;    adc    edx,edx    ; edx = CF.  1B shorter than setc dl, but requires edx=0 to start
    setc   al
    movzx  edx, al
    mov    [edi], edx ; store the carry-out into an extra limb beyond limbcount
    shl    edx, 2
    ;; Branching to handle the truncation would break the data-dependency (of pointers) on carry-out from this iteration
    ;;  and let the next iteration start, but we bottleneck on the front-end (9 uops)
    ;;  not the loop-carried dependency of the inner loop (2 cycles for adc->cmp -> flag input of adc next iter)
    ;; Since the pattern isn't perfectly regular, branch mispredicts would hurt us

    ; keep -1024 in ebx.  Using bl for the limb counter leaves bl zero here, so it's back to -1024 (or -2048 or whatever)
    mov    eax, esp
    and    esp, 4               ; only works if limbcount is even, otherwise we'd need to subtract limbcount first.

    and    edi, ebx  ; -1024    ; revert to start of buffer, regardless of offset
    add    edi, edx             ; read offset in next iter's src
    ;; maybe   or edi,edx / and edi, 4 | -1024?  Still 2 uops for the same work
    ;;  setc dil?

    ;; after adjusting src, so this only affects read-offset in the dst, not src.
    or     edx, esp             ; also set r8d if we had a source offset last time, to handle the 2nd buffer
    mov    esp, edi

;    xchg   edi, esp   ; Fibonacci: dst and src swap
    and    eax, ebx  ; -1024

    ;; mov    edi, eax
    ;; add    edi, edx
    lea    edi, [eax+edx]
    neg    edx            ; negated read-write offset used with store instead of load, so adc can micro-fuse

    mov    bl, limbcount/unrollfac
    ;; Last instruction must leave CF clear for next iter
;    loop .fibonacci  ; Maybe 0.01% slower than dec/jnz overall
;    dec ecx
    sub ecx, 1                  ; clear any flag dependencies.  No faster than dec, at least when CF doesn't depend on edx
    jnz .fibonacci

パフォーマンス:

 Performance counter stats for './fibonacci-1G-performance' (3 runs):

      62280.632258      task-clock (msec)         #    1.000 CPUs utilized            ( +-  0.07% )
                 0      context-switches:u        #    0.000 K/sec                  
                 0      cpu-migrations:u          #    0.000 K/sec                  
                 3      page-faults:u             #    0.000 K/sec                    ( +- 12.50% )
   273,146,159,432      cycles                    #    4.386 GHz                      ( +-  0.07% )
   757,088,570,818      instructions              #    2.77  insn per cycle           ( +-  0.00% )
   740,135,435,806      uops_issued_any           # 11883.878 M/sec                   ( +-  0.00% )
   966,140,990,513      uops_executed_thread      # 15512.704 M/sec                   ( +-  0.00% )
    75,953,944,528      resource_stalls_any       # 1219.544 M/sec                    ( +-  0.23% )
       741,572,966      idq_uops_not_delivered_core #   11.907 M/sec                    ( +- 54.22% )

      62.279833889 seconds time elapsed                                          ( +-  0.07% )

これは同じFib(1G)の場合で、73秒ではなく62.3秒で同じ出力を生成します。(273.146Gサイクル、対322.467G。すべてがL1キャッシュでヒットするので、コアクロックサイクルだけを見る必要があります。)

総数をはるかに下回る合計uops_issued数に注意してくださいuops_executed。これは、それらの多くがマイクロ融合されたことを意味します。融合ドメイン(issue / ROB)では1 uopですが、非融合ドメイン(scheduler / execution units)では2 uopです。そして、そのいくつかは、発行/名前変更の段階で削除されました(発行movコピーxorは必要ですが、実行ユニットは不要です)。uopを削除すると、逆にカウントのバランスが崩れます。

branch-misses1Gから約40万まで低下しているため、アンロールが機能しました。 resource_stalls.anyこれは、フロントエンドがボトルネックではなくなったことを意味します。代わりに、バックエンドが遅れてフロントエンドを制限しています。 idq_uops_not_delivered.coreフロントエンドがuopを配信しなかったが、バックエンドストールしなかったサイクルのみをカウントします。これは素晴らしく低いもので、フロントエンドのボトルネックがほとんどないことを示しています。


おもしろい事実:Pythonバージョンは、追加するのではなく10で割った時間の半分以上を費やしています。(a/=10withを置き換えるa>>=64と2倍以上高速になりますが、バイナリ切り捨て!= 10進切り捨てのため結果が変わります。)

もちろん、私のasmバージョンは、この問題サイズに合わせて最適化されており、ループの反復カウントはハードコーディングされています。任意精度の数値をシフトしてもコピーされますが、私のバージョンでは、次の2回の反復でそれをスキップするためにオフセットから読み取ることができます。

Pythonバージョン(Arch Linuxの64ビットpython2.7)のプロファイルを作成しました

ocperf.py stat -etask-clock,context-switches:u,cpu-migrations:u,page-faults:u,cycles,instructions,uops_issued.any,uops_executed.thread,arith.divider_active,branches,branch-misses,L1-dcache-loads,L1-dcache-load-misses python2.7 ./fibonacci-1G.anders-brute-force.py
795231787455468346782938519619714818925554218523439891345303993734324668618251937005099962613655677933248203572322245122629171445627564825949953061211130125549987963951605345978901870056743994684484303459980241992404375340195011483010723426503784142698039838736078428423199645734078278420076776090777770318318574465653625351150285171596335102399069923259547132267036550648243596658688604862715971691635144878852742743550811390916796390738039824284803398011027637054426428503274436478119845182546213052952963333981348310577137012811185112824713631141420831898380252690791778709480221775085968511636388337484742803673714788207995668880750915837224945143751932016258200200053079830988726125702820190750937055423293110708497685471583358562391045067944912001156476292564914450953190468498441700251208650402077901250135617787419960508555831719090539513446891944331302682481336323419049437559926255302546652883812263943360048384953507064771198676927956854879685520768489774177178437585949642538435587910579974100118580

 Performance counter stats for 'python2.7 ./fibonacci-1G.anders-brute-force.py':

     755380.697069      task-clock:u (msec)       #    1.000 CPUs utilized          
                 0      context-switches:u        #    0.000 K/sec                  
                 0      cpu-migrations:u          #    0.000 K/sec                  
               793      page-faults:u             #    0.001 K/sec                  
 3,314,554,673,632      cycles:u                  #    4.388 GHz                      (55.56%)
 4,850,161,993,949      instructions:u            #    1.46  insn per cycle           (66.67%)
 6,741,894,323,711      uops_issued_any:u         # 8925.161 M/sec                    (66.67%)
 7,052,005,073,018      uops_executed_thread:u    # 9335.697 M/sec                    (66.67%)
   425,094,740,110      arith_divider_active:u    #  562.756 M/sec                    (66.67%)
   807,102,521,665      branches:u                # 1068.471 M/sec                    (66.67%)
     4,460,765,466      branch-misses:u           #    0.55% of all branches          (44.44%)
 1,317,454,116,902      L1-dcache-loads:u         # 1744.093 M/sec                    (44.44%)
        36,822,513      L1-dcache-load-misses:u   #    0.00% of all L1-dcache hits    (44.44%)

     755.355560032 seconds time elapsed

(括弧)の数字は、パフォーマンスカウンターがサンプリングされた時間の長さです。HWがサポートするよりも多くのカウンターを見ると、perfは異なるカウンターと外挿の間を回転します。同じタスクを長期間実行する場合は、これでまったく問題ありません。

perfsysctlを設定した後にkernel.perf_event_paranoid = 0(またはperfrootとして実行して)実行した場合、測定され4.400GHzます。 cycles:u割り込み(またはシステムコール)に費やされた時間はカウントされず、ユーザー空間のサイクルのみがカウントされます。私のデスクトップはほぼ完全にアイドル状態でしたが、これは典型的なものです。


20

Haskell、83 61バイト

p(a,b)(c,d)=(a*d+b*c-a*c,a*c+b*d)
t g=g.g.g
t(t$t=<<t.p)(1,1)

出力(F 1000000000F 1000000001)。私のラップトップでは、1.35 GiBのメモリを使用して、133秒以内に左括弧と最初の1000桁を正しく印刷します。

使い方

フィボナッチ回帰は、行列累乗法を使用して解決できます。

[ F i − 1F i ; F iF i + 1 ] = [0、1; 1、1] i

これらのアイデンティティの導出元:

[ F i + j − 1F i + j ; F i + jF i + j + 1 ] = [ F i − 1F i ; F iF i + 1 ]⋅[ F j − 1F j ; F jF j + 1 ]、
F i + j = F i+ 1 F j + 1F i − 1 F j − 1 = F i + 1 F j + 1 −(F i + 1F i)(F j + 1F j)、
F i + j + 1 = F I F J + F I + 1 のF J + 1

このp関数は、(F iF i + 1)および(F jF j + 1)が与えられた場合に(F i + jF i + j + 1)を計算します。(F iF i + 1)について書くと、=になります。f np (f i) (f j)f (i + j)

その後、

(t=<<t.p) (f i)
= t ((t.p) (f i)) (f i)
= t (p (f i).p (f i).p (f i)) (f i)
= (p (f i).p (f i).p (f i).p (f i).p (f i).p (f i).p (f i).p (f i).p (f i)) (f i)
= f (10 * i)

(t$t=<<t.p) (f i)
= ((t=<<t.p).(t=<<t.p).(t=<<t.p)) (f i)
= f (10^3 * i)

t(t$t=<<t.p) (f i)
= ((t$t=<<t.p).(t$t=<<t.p).(t$t=<<t.p)) (f i)
= f (10^9 * i)

プラグインf 1= (1,1)


12

Mathematica、15 34 バイト

Fibonacci それ自体は私のコンピューター上で6秒かかります。フロントエンドで95(+/- 5)を表示する。

Fibonacci@1*^9&

ここに画像の説明を入力してください

最初の1000桁(34バイト): ⌊Fibonacci@1*^9/1*^208986640⌋&

テスト1

より長いがより速いToString@Fibonacci@1*^9~StringTake~1000&

テストのスクリーンショット


1
6秒?どのようなコンピューターを実行していますか?私には140秒かかりました!(また、文字列に変換して最初の1000文字を取得するのに、計算するよりも実際に10倍長くかかりますか?)
numbermaniac

1
@numbermaniac申し訳ありませんが、フロントエンドが数字を表示するのにかなり時間がかかることを明確にする必要があります。
キーガン

1
@numbermaniac:それらの時代は私を本当に驚かせません。内部的にはフィボナッチの結果はおそらくbase2にあり、IIRCのN番目のフィボナッチ数の計算はO(log(n))操作で実行できます。Mathematicaは、BigIntegerの大規模な追加を単に強引に行うだけではありません。IDKという言語; たぶん、71.5MB BigIntegerを実際に作成しないように、部分的に遅延評価を使用しているのかもしれません。
ピーターコーデス

2
@numbermaniac:さらに重要なことは、内部表現はBASE2であり、base10文字列に変換繰り返す必要分割 10.整数分割によってれるはるかに遅く、64ビット整数の整数乗算よりも、それは二登録拡張精度のためだけに悪いようです(悪くない場合は、かなり良い除算ハードウェアを備えたごく最近のx86 CPUでも、除算よりも乗算の方がパイプライン処理が優れているため)。私はそれも10のような小さな定数除数のために、任意の精度に悪いようだと仮定
ピーター・コルド

1
この質問に対するx86マシンコードの回答を探していましたが、ずっと数字を10進数に保つことを検討していました。それは主に、拡張精度の除算ループをまったく必要としないことで実装を短縮することでした。(バイトあたり2桁(0..99)、または32ビットチャンクごとに0..1e9-1であると考えていたため、各チャンクは一定数の10進数になり、単に使用できますdiv)。私がやったのは、おそらく私がすべての仕事をこなすためのゴルフの充実した機能を手に入れるまでに、この質問を人々が見ていたからでしょう。しかし、いくつかの答えが示すように、明らかにブルートフォースは機能します。
ピーターコーデス

11

Python 2、70バイト

a,b=0,1
i=1e9
while i:
 a,b=b,a+b;i-=1
 if a>>3360:a/=10;b/=10
print a

これはラップトップで18分31秒で実行され、正しい1000桁の数字が生成されました74100118580(正しい数字は74248787892)。


ブルートフォースと作業の節約の素晴らしい組み合わせ。
ピーターコーデス

これは、かなり単純なブルートフォースアプローチが機能することを示しているため、x86マシンコードでこれを実装することを考えていました。もちろん、100〜200バイトで動作し、すべての拡張精度の処理はもちろん手動で行うことができますが、特にゴルフ+最適化にはかなりの開発時間がかかります。私の計画はbase10 ** 9の32ビットチャンクでしたので、1006桁に切り捨てるのは簡単で、任意精度の除算なしで10進数文字列に変換するのは簡単です。divチャンクごとに9桁の10進数を作成するループ。ADCの代わりにcmp / cmovと2xADDを使用して追加中に実行します。
ピーターコーデス

以前のコメントを入力するのに十分だと考えて、私は夢中になりました。私はそのアイデアを使用して106バイトのx86 32ビットマシンコードで実装し、このPythonバージョンではデスクトップで12min35sで1分13秒で実行しました(ほとんどの時間を10で割って費やしています拡張精度base2数!)
ピーターコーデス

10

Haskell、78バイト

(a%b)n|n<1=b|odd n=b%(a+b)$n-1|r<-2*a*b-a*a=r%(a*a+b*b)$div n 2
1%0$2143923439

オンラインでお試しください!

TIOで48秒かかりました。Pythonのanswerと同じ再帰式ですが、切り捨てはありません。

定数214392343910**9-1、バイナリで反転され、最後に1が追加されます。逆に2進数を反復処理することは、の2進数を反復処理することをシミュレートします10**9-1。これを計算するよりもハードコードする方が短いようです。


9

ハスケル202 184 174 173 170 168 164 162バイト

(a,b)!(c,d)=a*c+b*d
l x=((34,55)!x,(55,89)!x)
f(a,b)|x<-l(a,b)=(x!l(b-a,a),x!x)
r=l.f
k=f.f.f
j=f.r.r.r.r
main=print$take 1000$show$fst$f$r$k$k$r$k$j$f$r$j$r(0,1)

オンラインでお試しください!

説明

これはフィボナッチ数を計算するためにかなり高速な方法を使用します。この関数lは2つのフィボナッチ数をf取り、10後にフィボナッチ数を計算し、n番目とn + 1番目のフィボナッチ数を取り、2n + 20番目と2n + 21番目のフィボナッチ数を計算します。10億を取得し、最初の1000桁を取得するために、それらを偶然に連鎖します。


関数をそれ自体でn回構成する演算子を実装することで、いくつかのバイトを節約できます。
user1502040

@ user1502040すなわち教会の数字。
フロリアンF

8

Haskell、81バイト

f n|n<3=1|even n=fk*(2*f(k+1)-fk)|1>0=f(k+1)^2+fk^2 where k=n`div`2;fk=f k
f$10^9

説明

f nnxnorの回答からの再帰を使用して、共通部分式を除去して、フィボナッチ数を再帰的に計算します。O(log(n))乗算を使用する投稿された他のソリューションとは異なり、O(n)乗算の複雑さのために、分岐係数が2のO(log(n))深再帰があります。

ただし、すべてが失われるわけではありません!ほとんどすべての呼び出しが再帰ツリーの最下部に近いため、可能な限り高速のネイティブ算術を使用し、巨大なビッグナムの多くの操作を回避できます。それは私の箱に数分で答えを吐き出します。


8

T-SQL、422 414 453バイト(検証済み、現在競合中!)

編集2:に変更され、数バイト増加しましたが、10億に達するのに十分な速度になりました!45時間29分で完了し、指定された文字列と照合して検証し、さらに8文字を表示します(丸めエラーのために正しい場合と正しくない場合があります)。INT BIGINT DECIMAL(37,0)

T-SQLにはネイティブの「巨大な数字」サポートがないため、1008文字の文字列を使用して、独自のテキストベースの巨大な数字加算器をロールする必要がありました。

DECLARE @a char(1008)=REPLICATE('0',1008),@ char(1008)=REPLICATE('0',1007)+'1',@c varchar(max),@x bigint=1,@y int,@t varchar(37),@f int=0o:SELECT @x+=1,@c='',@y=1i:SELECT @t=CONVERT(DECIMAL(37,0),RIGHT(@a,36))+CONVERT(DECIMAL(37,0),RIGHT(@,36))+@f,@a=RIGHT(@a,36)+@a,@=RIGHT(@,36)+@,@c=RIGHT(REPLICATE('0',36)+@t,36)+@c,@y+=1IF LEN(@t)>36SET @f=1 ELSE SET @f=0IF @y<29GOTO i
IF @f=1SELECT @a='0'+@,@='1'+@c ELSE SELECT @a=@,@=@c
If @x<1e9 GOTO o
PRINT @

コメント付きのフォーマットされたバージョンは次のとおりです。

DECLARE @a char(1008)=REPLICATE('0',1008)       --fib(a), have to manually fill
       ,@ char(1008)=REPLICATE('0',1007)+'1'    --fib(b), shortened variable
       ,@c varchar(max), @x bigint=1, @y int, @t varchar(37), @f int=0
o:  --outer loop
    SELECT @x+=1, @c='', @y=1
    i:  --inner loop
        SELECT @t=CONVERT(DECIMAL(37,0),RIGHT(@a,36))      --adds last chunk of string
                 +CONVERT(DECIMAL(37,0),RIGHT(@,36)) + @f
              ,@a=RIGHT(@a,36)+@a                          --"rotates" the strings
              ,@=RIGHT(@,36)+@
              ,@c=RIGHT(REPLICATE('0',36)+@t,36)+@c        --combines result
              ,@y+=1
        IF LEN(@t)>36 SET @f=1 ELSE SET @f=0               --flag for carrying the 1
     IF @y<29 GOTO i                                       --28 * 36 digits = 1008 places
     IF @f=1 SELECT @a='0'+@, @='1'+@c                     --manually carries the 1
        ELSE SELECT @a=@, @=@c
If @x<1e9 GOTO o
PRINT @

基本的に、2つのフィボナッチ変数を表す1008文字のゼロで埋められた文字列を手動で操作@aしてい@ます。

私はそれらを追加8 18(管理可能な数値型に変換し、最後の36桁剥離して、一度に36桁の数字をDECIMAL(37,0)別の長い文字列に戻ってそれを壊し、その後、それらを加算します)@c。I次に、「回転」@a@前方に最後の36桁を移動させて、処理を繰り返します。28回転* 36桁ですべての1008がカバーされます。手動で「1つを運ぶ」必要があります。

数値が文字列の長さを超え始めると、「左にシフト」し、精度を失い始めますが、エラーは余分な文字の範囲内です。

INTとBIGINTで満たされたSQLテーブルを同様のロジックで使用してみましたが、劇的に遅くなりました。奇妙な。


7
会社のリソースの印象的な誤用!
-davidbak

6

PARI / GP、45バイト

\p1100
s=sqrt(5)
((1+s)/2)^1e9/s/1e208986640

どういうわけか\p1000十分ではありません。これは32ビットシステムでは機能しません。最終的な分割は、科学表記法の小数点を避けることです。



1

ルビー、63バイト

男、私はルビーのゴルフが苦手です。しかし、BigIntクラスはこの種のことに対して驚異的です。Anders Kaseorgと同じアルゴリズムを使用します。

require 'matrix'
m=Matrix
puts m[[1,1],[1,0]]**10**9*m[[1],[1]]

それは本当にあなたに千桁を取得しますか?
dfeuer
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.