Aşırı Fibonacci


47

Bu web sitesinde milyarlarca Fibonacci zorluğu var, bu yüzden milyarlarca kez tekrarlanan bir Fibonacci zorluğuyla işlerin canlanmasına izin verin!

Buradaki zorluk, 1000.000.000'inci Fibonacci sayısının ilk 1000 ondalık basamağını olabildiğince kısa bir programla vermektir. Bu daha sonra isteğe bağlı olarak, rakamların geri kalanı dahil ancak bunlarla sınırlı olmamak üzere seçtiğiniz herhangi bir ek çıktıyla izlenebilir.

Ben kongre o kullanıyorum fib 0 = 0, fib 1 = 1.

Programınız, çalıştırmanız ve doğruluğunu onaylamanız için yeterince hızlı olmalıdır. Bu amaçla, işte ilk 1000 rakam:

7952317874554683467829385196197148189255542185234398913453039937343246686182519370050999626136556779332482035723222451226291714456275648259499530612111301255499879639516053459789018700567439946844843034599802419924043753401950114830107234265037841426980398387360784284231996457340782784200767760907777703183185744656536253511502851715963351023990699232595471322670365506482435966586886048627159716916351448788527427435508113909167963907380398242848033980110276370544264285032744364781198451825462130529529633339813483105771370128111851128247136311414208318983802526907917787094802217750859685116363883374847428036737147882079956688807509158372249451437519320162582002000530798309887261257028201907509370554232931107084976854715833585623910450679449120011564762925649144509531904684984417002512086504020779012501356177874199605085558317190905395134468919443313026824813363234190494375599262553025466528838122639433600483849535070647711986769279568548796855207684897741771784375859496425384355879105799

Your program must be fast enough for you to run it and verify its correctness.Peki ya hafıza?
Stephen,

5
@ guest44851 diyor ki kim? ;)
user1502040

1
Eğer bariz gideceksem, bence bir a+=b;b+=a;döngü (belki de Java BigInteger ile) bariz seçimdir, en azından performansı düşünürseniz. Özyinelemeli bir uygulama her zaman benim için korkunç derecede yetersiz görünüyordu.
Peter Cordes

2
Çok sayıda insanı yerel olarak desteklemeyen bir dilde görmek isterdim!
BradC

1
@BradC: Ben de öyle düşünüyordum. Geliştirmek, hata ayıklamak, optimize etmek ve golf oynamak yaklaşık 2 gün sürdü, ancak şimdi x86 32 bitlik makine kod cevabım hazır (dizgeye dönüştürme ve write()sistem çağrısı yapma dahil 106 bayt ). Performans gereksinimini seviyorum, bu benim için daha eğlenceli oldu.
Peter Cordes

Yanıtlar:


34

Python 2 + sympy, 72 bayt

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

Çevrimiçi deneyin!

Jeff Dege
-1 bayt (1000 -> 1e3 Zachar sayesinde)
-2 bayt sayesinde -10
bayt , Zachar'ı sayesinde Python 2'ye geçerek -Geçen Aşan-Erik sayesinde gereksiz değişkenleri kaldırarak -2 bayt
-3 11'ing ile bayt -11thePirateBay sayesinde -3 değiştirerek bayt strnotjagan için backticks sayesinde

şimdi OP'nin gönderilmemiş haskell çözümünü yeniyor!


from sympy import*;sqrtimport sympy;sympy.sqrt
@JonathanAllan

Vay bu hızlı, bu nasıl çalışıyor?
Kritixi Lithos,

Bunun, fibonacchi sayıları için üstel yaklaşım kullandığını ve sadece ilk e3 rakamının gerekli olduğu detayından faydalandığını düşünüyorum çünkü bu, yaklaşımdan sapma ile ilgili herhangi bir sorunu otomatik olarak ortadan kaldırmaktadır. Bu doğru mu?
Fabian Röling,

@Fabian sympyPython için sembolik bir matematik paketidir, bu yüzden en azından çok büyük sayılara kadar (bu sayı yeterince büyük değildir) yuvarlama hatasıyla ilgili herhangi bir problem yoktur. O zaman bana ilk 1e3 hanesini vereceğim için hesapladım çünkü aksi halde .evalf(1e3)parçayı çıkarırsanız , bana çok kısa bir bilimsel gösterim gösterimi veriyor.
HyperNeutrino,

1
Senfoni python'un standart kütüphanesinin bir parçası olmadığı için, karakter sayınıza senfonyanın kaynağını dahil etmediğiniz sürece bu cevap geçerli görünmüyor ... ya da kod golf kurallarını tamamen yanlış mı yorumluyorum?
thegreatemu

28

Python 2 , 106 bayt

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

Çevrimiçi deneyin!

Kitaplık yok, sadece tamsayı aritmetiği. Neredeyse anında çalıştırılır.

Çekirdek, böl ve ele geçir kimliğidir:

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

Bu bize (a,b) = (f(n),f(n+1))iki katına kadar güncelleme yapmamızı sağlar n -> 2*n. Almak istediğimizden n=10**9, bu sadece log_2(10**9)=30tekrar eder. Biz inşa nkadar 10**9defalarca yaparak n->2*n+cher rakam için conun ikili genişleme. Ne zaman c==1, iki katına değer 2*n -> 2*n+1bir adım Fibonacci kayması ile yukarı kaydırılır(a,b)=(b+a,b)

Değerleri a,byönetilebilir kılmak için , ilk 1006rakamlarını 10, altındakileri zemine bölerek saklıyoruz 2**3340 ~ 1e1006.


:buzda! süslü hazır kitaplıklar lol kullanmaz. : D
HyperNeutrino

Daha hoş ama daha az golfif bir rekürensi bulmuştum a,b,c=a*a+b*b,a*a-c*c,b*b+c*c.
Neil,

21

x86 32 bit makine kodu (Linux sistem çağrılarıyla birlikte): 106 105 bayt

changelog: hızlı sürümde bir bayt kaydetti, çünkü bir off-one sabiti Fib (1G) sonucunu değiştirmiyor.

Ya da versiyonu (Skylake üzerinde)% 18 daha yavaş 102 bayt (kullanarak mov/ sub/ cmcyerine lea/ ' cmp, iç döngü içinde, en taşıma-out ve sarma oluşturmak için 10**9yerine 2**32). Veya en içteki döngüde taşıma işleminde bir dal ile ~ 5.3x daha yavaş bir sürüm için 101 bayt. (% 25,4 şube-yanlış tahmin oranı ölçtüm!)

Ya da baştaki sıfıra izin verilirse 104/101 bayt. (Çıktının 1 basamağını atlayarak sabit koda 1 bayt alır, bu Fib için gerekli olan şeydir (10 ** 9).

Ne yazık ki, TIO'nun NASM modu -felf32derleyici bayrağında yok sayar gibi görünüyor . İşte yine de , tam kaynak kodumla, yorumların içindeki deneysel fikirlerin karışıklığıyla bir bağlantı .

Bu tam bir programdır . İlk 1000 basamağı Fib (10 ** 9) ve ardından bazı ekstra basamağı (son birkaç tanesi yanlış olanı), ardından bazı çöp baytlarını (yeni satır hariç) yazdırır. Çöplerin çoğu ASCII değildir, bu nedenle boru çekmek isteyebilirsiniz cat -v. Yine de terminal emülatörümü (KDE konsole) kırmıyor. "Çöp baytları" Fib'i (999999999) saklıyor. Zaten -1024bir kayıt defterinde bulundum , bu yüzden uygun boyuttan 1024 bayt yazdırmak daha ucuzdu.

Sadece makine kodunu sayıyorum (statik çalıştırılabilirimin metin bölümünün boyutu), onu ELF tarafından çalıştırılabilir kılan kabartmayı değil. ( Çok küçük ELF dosyaları çalıştırılabilir , ancak bununla uğraşmak istemedim). BSS yerine yığın bellek kullanmak daha kısa olduğu için herhangi bir metadata bağlı olmadığım için ikili sistemde başka hiçbir şeyi saymamayı haklı çıkarabilirim. (Soyulmuş bir statik ikili sayı üretme normal şekilde 340 baytlık bir ELF çalıştırılabilir hale getirir.)

Bu kodun dışında C'den çağırabileceğiniz bir işlev oluşturabilirsiniz. Yığın işaretçisini (belki bir MMX kayıt defterinde olabilir) ve diğer bazı ek yükleri kaydetmek / geri yüklemek birkaç haneye mal olur, ancak dize ile dönerek de baytları kurtarır. bellekte, write(1,buf,len)sistem çağrısı yapmak yerine . Makine kodunda golf oynamanın bana burada biraz durgunluk kazandırması gerektiğini düşünüyorum, çünkü hiç kimse yerel dilde geniş bir hassasiyet olmadan herhangi bir dilde bir cevap bile yayınlamamıştı, ancak bunun bir işlevsel versiyonunun tümüyle yeniden golf oynamadan 120 bayt altında olması gerektiğini düşünüyorum. şey.


Algoritma:

kaba kuvvet a+=b; swap(a,b), sadece öncü> = 1017 ondalık basamak tutmak için gerektiği gibi kesiliyor. Bilgisayarımda 1dk13'lerde çalışıyor (veya 322.47 milyar saat döngüsü + -% 0.05) (ve birkaç ekstra kod baytında% birkaç daha hızlı olabilir veya loop unroll işleminden çok daha büyük kod boyutu ile 62 saniyeye kadar olabilir. akıllı matematik, sadece daha az ek yük ile aynı işi yapıyorum). Bu dayanıyor @ AndersKaseorg Python uygulaması bilgisayarım (4.4GHz Skylake i7-6700k) üzerinde 12min35s çalışır. Her iki sürümde de L1D önbellek kaçırmadığı için DDR4-2666 benim için önemli değil.

Python'un aksine genişletilmiş sayıları, ondalık basamakları serbest bırakan bir biçimde saklarım . 32-bit tamsayı başına 9 ondalık basamaklı grupları saklarım, böylece bir işaretçi ofseti düşük 9 basamağı atar. Bu, 10 milyarlık bir güç olan 1 milyar taban kazanıyor. (Bu zorluğun 1 milyarda bir Fibonacci sayısına ihtiyaç duyması tam bir tesadüf, ancak beni iki bayt ile iki ayrı sabit arasında kurtarıyor.)

GMP terminolojisini takiben , genişletilmiş hassasiyetli bir sayının her bir 32-bit öbekine "uzuv" denir. Ekleme yapılırken gerçekleştirilmenin 1e9 ile karşılaştırmalı olarak manuel olarak üretilmesi gerekir, ancak daha sonra normalde bir sonraki uzuv için olağan ADCtalimatın girişi olarak kullanılır . (Ayrıca [0..999999999]2 ^ 32 ~ = 4.295e9 yerine menzile manuel olarak sarmam gerekiyor. Karşılaştırmadan elde edilen sonucu kullanarak bunu dalsız olarak lea+ ile yapıyorum cmov.)

Son uzuv sıfır olmayan bir çıkıntı ürettiğinde, dış döngünün sonraki iki yinelemesi normalden yüksek olan 1 uzuvdan okunur ancak yine de aynı yere yazılır. Bu, memcpy(a, a+4, 114*4)1 uzuv tarafından sağa kaymaya benzer, ancak sonraki iki toplama döngüsünün bir parçası olarak yapılır. Bu, her ~ 18 tekrarlamada gerçekleşir.


Boyut tasarrufu ve performans için kesmek:

  • Gibi olağan şeyler lea ebx, [eax-4 + 1]yerine mov ebx, 1bunu biliyorum zaman eax=4. Ve yavaşlığın sadece küçük bir etkisi loopolduğu yerlerde kullanmak .LOOP

  • adcİç döngüdeki tamponun başlangıcına yazmaya devam ederken okuduğumuz göstericileri uzaklaştırarak, 1 ekstremite için ücretsiz kesin . Biz okuduk [edi+edx]ve yazıyoruz [edi]. Böylece hedef için bir okuma-yazma ofseti alabiliriz edx=0ya 4da alabiliriz. Bunu iki ardışık yineleme için yapmalıyız, önce ikisini de, sonra da yalnızca dst'i dengeleyerek. esp&4İşaretçileri arabelleklerin ön tarafına sıfırlamadan önce bakarak 2. vakayı saptarız ( &= -1024arabellekleri hizalı olduğu için kullanarak ). Koddaki yorumları görün.

  • Linux işlem başlatma ortamı (statik bir çalıştırılabilir için) çoğu kaydı sıfırlar ve esp/ altındaki yığın belleği rspsıfırlanır. Programım bundan faydalanıyor. Bunun çağrılabilir işlevli bir sürümünde (ayrılmamış yığının kirlenebileceği), sıfırlanmış bellek için BSS'yi kullanabilirim (işaretçileri ayarlamak için belki de 4 bayt pahasına). Sıfırlama edx2 bayt alır. X86-64 System V ABI bunların her ikisini de garanti etmiyor, ancak Linux uygulamasının sıfır olması (çekirdeğin dışına bilgi sızıntısını önlemek için). Dinamik olarak bağlı bir işlemde, /lib/ld.soönce çalışır _startve yazmaçları sıfır bırakmaz (ve yığın işaretçisinin altındaki bellekte çöp).

  • Ben tutmak -1024içinde ebxdöngüler kullanılması dışından için. blSıfır ile biten iç döngüler için bir sayaç olarak kullanın (bu düşük bayttır -1024, bu nedenle döngü dışında kullanım için sabiti geri yükler). Intel Haswell ve sonraki sürümlerde düşük kayıtlar için kısmi kayıt birleştirme cezaları yoktur (ve aslında bunları ayrı olarak yeniden adlandırmazlar) , bu nedenle AMD'de olduğu gibi tam kayıt defterine bağımlılık vardır (burada sorun değil). Nehalem ve daha önce, bu birleşme sırasında kısmi sicil tezgahları olan korkunç olurdu. Kısmi xorregs yazdığım ve sonra sıfırlamadan veyamovzx, genellikle bazı önceki kodların üst baytları sıfırladığını bildiğim için ve AMD ve Intel SnB ailesinde yine iyi, ancak Intel öncesi Sandybridge'de yavaş.

    1024Stdout ( sub edx, ebx) ' a yazmak için bayt sayısını kullanıyorum , bu yüzden programım Fibonacci rakamlarından sonra bazı çöp baytlarını yazdırıyor, çünkü mov edx, 1000maliyeti daha fazla bayt.

  • (kullanılmıyor) adc ebx,ebx1 bayt vs tasarruf Ebx = CF almak için EBX = 0 ile setc bl.

  • dec/ jnzbir adcdöngü içinde adcIntel Sandybridge ve daha sonra bayrakları okuduğunda kısmi bayrak durmasına neden olmadan CF'yi korur . Daha önceki işlemcilerde kötü , ancak Skylake'de AFAIK ücretsiz. Ya da en kötüsü, fazladan bir şey.

  • Aşağıdaki belleği espdev bir kırmızı bölge olarak kullanın . Bu, tam bir Linux programı olduğundan, herhangi bir sinyal işleyicisi yüklemediğimi ve başka hiçbir şeyin eşzamansız olarak kullanıcı alanı yığın hafızasına zarar vermeyeceğini biliyorum. Diğer işletim sistemlerinde durum böyle olmayabilir.

  • Yararlanın yığın-motor kullanarak sorunu bant genişliğini uop kurtarmak için pop eaxyerine (1 uop + ara sıra yığını senkron uop) lodsd(Haswell / Skylake, IVB üzerinde 3 2 UOPs ve önceki göre Agner Fog'un talimat tabloları )). IIRC, bu çalışma süresini yaklaşık 83 saniyeden 73'e düşürdü. Src ve dst arabellekleri arasındaki farkı olduğu movgibi, indekslenmiş bir adresleme moduyla a kullanarak aynı hızı elde edebilirim . (Fibonacci yinelemeleri için swapping src ve dst'in bir kayması olarak ofset registerını reddetmek zorunda kalacak şekilde iç döngü dışındaki kodu daha karmaşık hale getirir.) Daha fazlası için aşağıdaki "performans" bölümüne bakın.mov eax, [edi+ebp]ebp

  • ilk yinelemeyi herhangi stcbir 1yerde bir belleğe kaydetmek yerine, bir geçiş (bir bayt ) vererek diziyi başlatın . Yorumlarda belgelenen başka probleme özel şeyler de var.

NASM listesi (makine kodu + kaynak) ile oluşturulmuştur nasm -felf32 fibonacci-1G.asm -l /dev/stdout | cut -b -28,$((28+12))- | sed 's/^/ /'. (Sonra, yorumlanan öğelerin bazı bloklarını elden kaldırdım, bu nedenle satır numaralandırmada boşluklar var.) Baştaki sütunları çıkarmak için YASM veya NASM'e besleyebilmeniz için kullanın 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

Muhtemelen bundan biraz daha fazla bayta golf oynamak için oda vardır, ancak 2 gün boyunca bunun için en az 12 saat geçirdim. Hıztan fedakarlık etmek istemiyorum, yeterince hızlı olmasına rağmen ve hızı düşürecek şekilde küçültmek için yer var . Gönderme nedenimin bir parçası kaba kuvvet bir asm sürümünü ne kadar hızlı yapabildiğimi gösteriyor. Eğer biri gerçekten asgari boyutta ama belki de 10 kat daha yavaş (örneğin, bayt başına 1 hane) gitmek istiyorsa, bunu bir başlangıç ​​noktası olarak kopyalamaktan çekinmeyin.

Elde edilen çalıştırılabilir (from yasm -felf32 -Worphan-labels -gdwarf2 fibonacci-1G.asm && ld -melf_i386 -o fibonacci-1G fibonacci-1G.o) 340B (sıyırılmış):

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

Verim

İç adcdöngü, Skylake'de 10 kaynaşık alan adıdır (her ~ 128 baytta +1 yığın senkronizasyonu), bu nedenle en iyi ön uç verimine sahip (yığın senkronizasyonu ihmallerini görmezden) Skylake'de ~ 2,5 döngü başına bir değer verebilir. . Kritik yol gecikmesi adc-> cmp- -> sonraki yinelemenin adcdöngü taşıma bağımlılık zinciri için 2 döngüdür, bu nedenle darboğaz yineleme başına ~ 2,5 döngü ön uç sayı sınırı olmalıdır.

adc eax, [edi + edx]yürütme bağlantı noktaları için 2 kaynaşık olmayan etki alanı kümesidir: load + ALU. Kod çözücüler içinde mikro-kaynaşmalar meydana gelir (1 kaynaşık alanlı etki alanı), ancak sorunlu aşamada laminatlanır, Dizine alınmış adresleme modundan dolayı Haswell / Skylake'de bile, 2 kaynaşık alanlı etki alanına katılır . Sanırım mikro kaynaşık kalacaktı add eax, [edi + edx], ama indekslenmiş adresleme modlarını saklamak mikro kaynaşmış zaten 3 girişe sahip olan girişlerde (bayrak, bellek ve hedef) işe yaramaz. Yazarken, olumsuz bir performansa sahip olmayacağını düşünüyordum, ama yanılmışım. Bu kesme işleminin yolu edx0 veya 4 olsun , her zaman iç çevrimi yavaşlatır .

Deposunu ayarlamak için edikullanarak ve yazarak dst için okuma-yazma ofsetini kullanmak daha hızlı olacaktır edx. Yani adc eax, [edi]/ ... / mov [edi+edx], eax/ lea edi, [edi+4]yerine stosd. Haswell ve daha sonra endekslenmiş bir mağazayı mikro sigortalı tutabilir. (Sandybridge / IvB de buna katılmazdı.)

Intel Haswell ve daha önceki sürümlerde adcve cmovc2c gecikmeyle her biri 2 uops . ( adc eax, [edi+edx]Haswell'de hala lamine edilmemiştir ve 3 kaynaşık alan adı birimi olarak yayınlanmaktadır). Broadwell ve daha sonra , uzun süredir AMD'de olduğu gibi, tek girişli talimatlar adcve cmovc(ve birkaç başka şey) FMA'dan (Haswell) daha fazlası için 3-girişe izin verir . (Bu, AMD'nin uzun süredir hassas GMP testlerinde iyi bir performans göstermesinin bir nedenidir.) Her neyse, Haswell'in iç halkası 12 opss (zaman zaman +1 yığın senkronizasyonu per) olmalıdır, en iyi durumda, yığın senkronizasyon uops göz ardı ederek.

Döngü içinde popbir balans olmadan kullanmak push, döngünün LSD'den (döngü akış detektörü) çalışamayacağı ve her seferinde uop önbellekten IDQ'ya yeniden okunması gerektiği anlamına gelir. Bir şey varsa, Skylake'de iyi bir şey, çünkü 9 ya da 10 uop ​​döngü her döngüde 4 uop'ta optimal olarak sorun çıkarmaz . Bu muhtemelen değiştirilmesi neden parçasıdır lodsdile popçok yardımcı oldu. (Bir yığın senkronizasyonu eklemek için odadan çıkmadığı için LSD aygıtları kilitleyemez .) (Bir mikro kod güncellemesi olan BTW, bir hata düzeltmek için LSD'yi tamamen Skylake ve Skylake-X üzerinde devre dışı bırakır. bu güncellemeyi almadan önce yukarıda.)

Haswell'de profilini çıkardım ve 381.31 milyar saat döngüsünde çalıştığını öğrendim (CPU frekansından bağımsız olarak, yalnızca L1D önbelleğini kullandığından bellek kullanmıyor). Ön uç sorun çıktısı, saat başına 3,72 sigorta alanı etki alanı, Skylake için 3,70'tir. (Çünkü Ama tabii döngüsü başına talimatları, 2.87 ila 2.42 aşağı oldu adcve cmovHaswell 2 UOPs bulunmaktadır.)

pushstosdmuhtemelen değiştirmek o kadar yardımcı olmaz, çünkü adc [esp + edx]her seferinde bir senkronizasyon senkronizasyonu tetiklerdi. Ve bir bayta mal olur, stdyani lodsddiğer yöne gider. ( mov [edi], eax/ lea edi, [edi+4]yerine stosdkoyma, 100 milyon iters için 32.909Mcycles'tan, 100 milyon iters için 31,954Mycle'a kadar olan bir galibiyettir. Görünüşe göre stosd, mağaza adresi / mağaza verisi mikro-kaynaşık değil, yani push+ yığın senkronizasyonu ile 3 uop olarak çözülür) uops hala daha hızlı olabilir stosd)

114B uzuvların 1G tekrarlamaları için ~ 322.47 milyar devirin gerçek performansı Skylake'deki hızlı 105B sürümü için iç döngünün her tekrarlaması için 2.824 devire ulaşmaktadır. (Aşağıdaki ocperf.pyçıktıya bakınız). Statik analizden tahmin ettiğimden daha yavaştır, fakat dış döngünün ve herhangi bir yığın-senkronizasyon ek yükünün yükünü görmezden geliyordum.

İçin sayaçlar Perf branchesve branch-missesgösteri olduğunu iç döngü mispredicts dış döngü başına (çekildiği değil son yineleme, üzerine) bir kez. Bu, fazladan zamanın bir kısmını da hesaba katar.


I kullanılarak, en iç döngü kritik yol için 3 döngü gecikme var yaparak kod büyüklüğü kurtarabilir mov esi,eax/ sub eax,ebp/ cmovc eax, esi/cmc (2 + 2 + 3 + 1 = 8B) yerine lea esi, [eax - 1000000000]/ cmp ebp,eax/ cmovc(6 + 2 + 3 = 11 B ). cmov/ stosdKritik yol kapalıdır. (Arasında artış-edi UOP stosdBu den ebp init talimatı değiştirerek bir 1B kaydetmek için kullanılan bir kısa bağımlılık zincir dışında, her yineleme çatal böylece., Mağazadan ayrı çalışabilir) lea ebp, [ecx-1]için mov ebp,eax, ama yanlış sahip olduğu keşfedilmiştirebpsonucu değiştirmedi. Bu, bir uzuvun bir taşıyıcıyı sarmak ve üretmek yerine tam olarak == 1000000000 olmasına izin verir, ancak bu hata Fib () büyüdüğümüzden daha yavaş yayılır, bu nedenle nihai sonucun baştaki 1k hanelerini değiştirmez. Ayrıca, sadece eklediğimizde bu hatanın kendi kendini düzeltebileceğini düşünüyorum, çünkü bir taşma taşımayacak bir uzuvda yer var. 1G + 1G bile 32 bitlik bir tamsayıdan taşmaz, bu nedenle sonunda yukarı doğru süzülür veya kesilir.

3c gecikme süresi 1 ekstra uop'tur, bu nedenle ön uç Skylake'de 2.75c döngü başına bir tane yayınlayabilir, arka uç tarafından çalıştırılandan yalnızca biraz daha hızlı olabilir. (Haswell'de hala kullandığından adcve toplamda 13 ay olacak ve cmoviter başına 3.25c'de ön taraftaki tıkanıklığı olacak).

Uygulamada Skylake'de (ekstremite başına 3.34 devir) 3 / 2.5 = 1.2 yerine 1.18'lik bir yavaşlama faktörü çalıştırıyor, ön uç darboğazı ile gecikme darboğazını değiştirmeden önce iç döngüde yığın senkronizasyonu olmadan bakmak için öngördüm UOPs. Yığın senkronizasyonu sadece hızlı sürüme zarar verdiğinden (gecikme yerine ön uçtan tıkanmış), açıklamak için fazla bir şey gerekmez. örneğin 3 / 2.54 = 1.18.

Diğer bir faktör ise, 3c gecikme versiyonunun kritik yol hala yürütülürken iç halkayı terk etme konusundaki yanlış tahminin tespit edilebildiğidir (çünkü ön uç arka uçtan öne geçebilir, böylece sıra dışı yürütme döngü çalışmasına izin verir. ters)), bu nedenle etkili yanlış tahmin ceza düşüktür. Bu ön uç çevrimlerini kaybetmek, arka uçların yetişmesini sağlar.

Öyle olmasaydı cmc, carrycout -> edx ve esp ofsetlerinin dalsız kullanımı yerine dış döngüde bir dal kullanarak 3c sürümünü hızlandırabiliriz . Veri bağımlılığı yerine kontrol bağımlılığı için Branş Tahmini + spekülatif yürütme adc, önceki iç döngüden gelenler hala uçuştayken bir sonraki yinelemenin döngüyü çalıştırmaya başlamasını sağlayabilir. Dalsız versiyonda, iç döngüdeki yük adresleri adc, son ekstremitenin sonundan itibaren CF'ye veri bağımlılığına sahiptir .

2c gecikmeli iç döngü versiyonu ön uçta darboğazlar oluşturur, böylece arka uç hemen hemen tutar. Dış döngü kodu yüksek gecikme süresi içeriyorsa, ön uç iç döngünün bir sonraki yinelemesinden uops çıkarmaya devam edebilir. (Ancak bu durumda dış döngü malzemeleri bol miktarda ILP'ye sahiptir ve yüksek gecikme süresi yoktur, bu nedenle arka uç, sıra dışı zamanlayıcıdaki uops ile çiğnemeye başladığında başaracak kadar şey yapmaz. girişleri hazır hale gelir).

### 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 %)bu sayı için 4 turdaki standart sapmadır. Bu kadar çok sayıda talimat vermesi ilginç. Bu 924 milyar tesadüf değil . Dış döngünün toplam 924 komut çalıştırdığını tahmin ediyorum.

uops_issuederimiş bir etki alanı sayısıdır (ön uç sorun bant genişliği için geçerlidir), uops_executederimiş bir etki alanı sayısıdır (yürütme bağlantı noktalarına gönderilen uops sayısı). Mikro-füzyon, 2 kaynaşmamış etki alanı uops'unu bir kaynaşık etki alanı uop'una paketler, ancak mov-eliminasyonu , bazı kaynaşmış etki alanı uçlarının herhangi bir yürütme portuna ihtiyaç duymadığı anlamına gelir. Uops ve kaynaşmış ve kaynaşmamış alan adlarının sayılması hakkında daha fazla bilgi için bağlantılı soruya bakınız. (Ayrıca Agner Fog'un kullanım tablolarına ve uç kılavuzuna ve SO x86 etiketi wiki'deki diğer faydalı bağlantılara bakın ).

Farklı şeyler ölçen bir başka çalışmada: L1D önbellek özlüyor, aynı iki 456B arabellek okumak / yazmak için beklendiği gibi, tamamen önemsiz. İç ilmek dalı, dış ilmek başına bir kez yanlış girer (ilmek bırakmak için alınmadığında). (Bilgisayar tamamen boşta olmadığı için toplam süre daha yüksektir. Muhtemelen diğer mantıksal çekirdek bir süre daha aktifti ve kesintilerde daha fazla zaman harcandı (kullanıcı alanı tarafından ölçülen frekans 4.400GHz'in altında olduğundan). Ya da çoklu çekirdekler çoğu zaman etkindi, maksimum turboyu düşürdü. cpu_clk_unhalted.one_thread_activeHT yarışmasının bir sorun olup olmadığını görmedim.)

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

Kodum Ryzen'de daha az sayıda döngüde çalışabilir, bu da döngü başına 5 uops verebilir (veya bazıları Ryzen'deki AVX 256b gibi 2-uut komut olduğunda 6). Ön stosduçunun ne yapacağından emin değilim , ki bu Ryzen'de 3 uops (Intel ile aynı). İç döngüdeki diğer talimatların Skylake ile aynı gecikme olduğunu düşünüyorum. (Buna dahil adc eax, [edi+edx], Skylake'e göre bir avantaj).


Sayıları bayt başına 1 ondalık basamak olarak kaydedersem, bu muhtemelen daha küçük olabilir, ancak belki 9 kat daha yavaş olabilir . Birlikte gerçekleştirme cmpve ayarlama yapma cmovaynı işe yarar, ancak işi 1/9 yapar. Bayt başına 2 ondalık basamak (temel-100, yavaşDAA 4 bit BCD değil ) de çalışır ve div r8/ add ax, 0x303099 basma sırasındaki 0-99 baytı iki ASCII basamağa dönüştürür. Ancak bayt başına 1 basamak olması gerekmez div, sadece döngü ve 0x30 eklenir. Baytları yazdırma sırasına göre saklarsam, bu 2. döngüyü gerçekten basitleştirir.


64 bit tam sayı başına 18 veya 19 ondalık basamak kullanmak (64 bit modunda), yaklaşık iki kat daha hızlı çalışmasını sağlar, ancak tüm REX önekleri ve 64 bit sabitleri için önemli kod boyutu tutar. 64 bit modunda 32 bit uzuvlar pop eaxyerine kullanılmasını önler lodsd. Hala kullanarak REX önekleri önlemek olabilir esp(kullanımını takas olmayan bir işaretçi sıfırdan kaydı olarak esive esp) yerine kullanarak r8dbir 8 kaydı olarak.

Çağrılabilir bir işlev sürümü oluşturuyorsanız, 64-bit'e dönüştürme ve kullanma r8d, kaydetme / geri yükleme işleminden daha ucuz olabilir rsp. 64 bit, bir bayt dec r32kodlamayı da kullanamaz (çünkü bir REX önekidir). Ama çoğunlukla dec bl2 bayt olanı kullandım . (Çünkü üst baytlarda bir sabite sahibim ebxve sadece sabitin düşük baytı olduğu için çalışan iç döngülerin dışında kullanıyorum 0x00.)


Yüksek performanslı versiyon

Maksimum performans için (kod-golf değil), iç döngüyü açmak istediğinizde, en fazla 22 yineleme çalıştıracaksınız, bu da şube belirleyicilerinin iyi yapması için yeterince kısa / alınmamış bir kalıptır. Deneylerimde, mov cl, 22bir .inner: dec cl/jnz .innerdöngü çok az yanlış anlamadan önce (% 0.05 gibi, iç döngünün tam çalışması başına bir taneden çok daha az), ancak mov cl,23iç döngü başına 0.35 ila 0.6 kez yanlış tahmin eder. 46özellikle kötüdür, iç döngü başına ~ 1.28 kat yanlış tahmin eder (100M dış döngü yineleme için 128M kat). 114Fibonacci döngüsünün bir parçası olarak bulduğum aynı iç döngüde tam olarak bir kez yanlış anlaşıldı.

Merak ettim ve denedim, iç döngüyü 6 ile açın %rep 6(çünkü 114 eşit şekilde bölüdür). Bu çoğunlukla şube özlerini ortadan kaldırdı. edxNegatif hale getirdim ve movmağazalar için dengeleme olarak kullandım , böylece adc eax,[edi]mikro kaynaşık kalabilirdi. (Ve böylece engelleyebildim stosd). Ben çekti leagüncellemek için edidışarı %rep, bloğun sadece 6 mağaza başına bir işaretçi güncelleme eriyor.

Dış döngüdeki tüm kısmi sicillerden de kurtuldum, bunun önemli olduğunu sanmıyorum. Dış döngünün sonunda CF'nin nihai ADC'ye bağlı olmayan bir şekilde hafifçe olmasına yardımcı olmuş olabilir, bu yüzden bazı iç döngü uçları başlayabilir. Dış döngü kodu muhtemelen biraz daha fazla optimize edilebildi, çünkü neg edxyaptığım son şeydi, xchgsadece 2 movkomutla değiştirdikten sonra (zaten 1'im vardı) ve 8-bit bırakma ile birlikte zincirleri yeniden düzenleme bir şeyler kaydet.

Bu sadece Fibonacci döngüsünün NASM kaynağıdır. Orijinal sürümün bu bölümü için bir değiştirme işlemidir.

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

Verim:

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

Bu aynı Fib (1G) için, aynı çıktıyı 73 saniye yerine 62,3 saniyede üretiyor. (273.146G döngü, 322.467G vs.. Her şey L1 önbelleğinde yer aldığından, çekirdek saat döngüleri gerçekten göz atmamız gereken tek şeydir.)

uops_issuedSayının çok altında , çok daha düşük toplam sayıma dikkat edin uops_executed. Bu, çoğunun mikro kaynaşmış olduğu anlamına gelir: kaynaşık alanda 1 uop (sorun / ROB), ancak kaynaşmamış alanda 2 uop (zamanlayıcı / yürütme birimleri)). Bu sayı, sorun / yeniden adlandırma aşamasında da ortadan kaldırıldı ( yayınlanması gereken ancak bir yürütme birimine ihtiyaç duymayan, sıfırlama movveya xorsıfırlama gibi ). Ortadan kaldırılmış uops, diğer şekilde sayım dengesizliği olur.

branch-misses1G'den ~ 400k'ya kadar düştü, bu yüzden unrolling çalıştı. resource_stalls.anyşimdi anlamlıdır, yani ön uç artık darboğaz değildir: bunun yerine arka uç arkaya gidiyor ve ön ucunu sınırlıyor. idq_uops_not_delivered.coreSadece ön uç UOPs teslim etmedi döngüleri sayar, ancak arka uç değildi durdu. Bu, hoş ve alçak, birkaç ön uç tıkanıklığı olduğunu gösterir.


Eğlenceli gerçek: python versiyonu, zamanının yarısından fazlasını ilave etmek yerine 10'a bölünerek geçiriyor. (Değiştirme a/=10ile a>>=64daha 2 faktör daha tarafından hızlarda o kadar, ama ikili bir kesilmeye çünkü sonucu değiştirir! = Ondalık kesilmesi.)

Asm versiyonum elbette özellikle bu problem boyutu için optimize edilmiştir, döngü yineleme sayıları sabit kodlanmıştır. İsteğe bağlı hassas bir numarayı değiştirmek bile kopyalayacaktır, ancak sürümüm sonraki iki yinelemenin bile atlaması için bir ofsetten okuyabilir.

Python sürümünü (Arch Linux'ta 64-bit python2.7) profilledim :

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

(Parens) içindeki sayılar, perf sayacının örneklendiği zamanın ne kadarıdır. HW'nin desteklediğinden daha fazla sayıcıya bakarken, perf farklı sayıcılar ve ekstrapolatlar arasında döner. Aynı işin uzun sürmesi için bu tamamen iyi.

Eğer perfsysctl ayarını yaptıktan sonra koştuysam kernel.perf_event_paranoid = 0(veya perfroot olarak çalıştırıyorsam ), bunu ölçecekti 4.400GHz. cycles:ukesintilerde (veya sistem çağrılarında) harcanan zamanı saymaz, yalnızca kullanıcı alanı döngülerini sayar. Masaüstüm neredeyse tamamen boştaydı, ancak bu normal.


20

Haskell, 83 61 bayt

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)

Çıkışlar ( F 1000000000 , F 1000000001 ). Dizüstü bilgisayarımda, sol paren ve ilk 1000 basamağı 133 saniye içinde 1.35 GiB bellek kullanarak doğru şekilde yazdırıyor.

Nasıl çalışır

Fibonacci yinelenmesi, matris üstelleştirmesi kullanılarak çözülebilir:

[ F i - 1 , F i ; F i , K i + 1 ] = [0, 1; 1, 1] i ,

bu kimlikleri türetdiğimiz şey:

[ F i + j - 1 , F i + j ; F i + j , K i + j + 1 ] = [ E i - 1 , F i ; F i , K i + 1 ] ⋅ [ F j - 1 , F j ; F j , F j + 1 ],
F i + j = F i+ 1 F j + 1 - F i - 1 F j - 1 = F i + 1 F j + 1 - ( F i + 1 - F i ) ( F j + 1 - F j ),
F i + j + 1 = F i F j + F i + 1 F j + 1 .

pFonksiyon değerlerini hesaplar ( F i + j , F i + j + 1 ) verilen ( F ı , K i + 1 ) ve ( F j , F j + 1 ). Yazma f niçin ( F i , K i + 1 ), elimizdeki p (f i) (f j)= f (i + j).

Sonra,

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

ve biz f 1= takın (1,1).


12

Mathematica, 15 34 bayt

Fibonacci bilgisayarımda kendisi ~ 6s alır. Ve ön görüntülemek için 95 (+/- 5) s.

Fibonacci@1*^9&

görüntü tanımını buraya girin

İlk 1000 hane (34 bayt): ⌊Fibonacci@1*^9/1*^208986640⌋&

test 1

Daha uzun ama daha hızlı ToString@Fibonacci@1*^9~StringTake~1000&:

test ekran görüntüsü


1
6 saniye mi? Ne tür bir bilgisayar kullanıyorsunuz? Benimki 140 saniye sürdü! (ayrıca, onu bir dizgeye dönüştürmek ve sadece hesaplamak yerine ilk 1000 karakteri almak için 10x daha uzun
sürüyor

1
@ sayı: Üzgünüm ön bilginin numarayı göstermesinin daha uzun sürdüğünü açıklığa kavuşturmam gerekir.
Keyu Gan

1
@ sayı_maniac: O zamanlar beni gerçekten şaşırtmadı. Dahili olarak Fibonacci sonucu muhtemelen base2'dedir ve Nth Fibonacci sayısını hesaplayan IIRC, O (log (n)) işlemlerinde yapılabilir; Mathematica, kesinlikle sadece büyük BigInteger eklemeleriyle zorla ilerlemiyor. Dili iyi yapan IDK; belki de 71.5 MB BigInteger oluşturmaktan kaçınmak için kısmen tembel bir değerlendirme kullanıyor.
Peter Cordes

2
@numbermaniac: Daha da önemlisi, iç temsil temel2 olduğunu ve bir Base10 dizeye dönüştürmek tekrar ettirilmesi bölünme 10. Tamsayı bölümü tarafından edilir çok yavaş 64 bitlik tamsayılar için tamsayı çarpma daha ve iki-kayıt uzatılmış hassasiyet için sadece kötü olarak bulunuyor (Daha kötüsü değilse, çarpma işlemi çok iyi bölme donanımına sahip x86 işlemcilerde bile bölmeden daha iyi bir şekilde boru hattı oluşturduğundan). 10 gibi küçük bir sürekli bölen için bile, keyfi hassasiyet için kötü olduğunu kabul ediyorum.
Peter Cordes

1
Bu sorunun cevabını bir x86 koduna bakıyordum ve sayılarımı her zaman ondalık tutmayı düşünüyordum. Bu, çoğunlukla genişletilmiş bir hassasiyet döngüsüne hiç ihtiyaç duymadan uygulamayı kısaltmaktı. (Bayt başına 2 basamak (0..99) veya 32bit yığın başına 0..1e9-1 olabilir, bu yüzden her yığın sabit bir ondalık basamak sayısına dönüşür ve kullanabilirim div). İnsanların muhtemelen bütün bu işleri yapan iyi bir golf fonksiyonuna sahip olduğumda bu soruya bakması nedeniyle durdum. Ancak görünüşe göre kaba kuvvet çalışabilir, bazı cevapların gösterdiği gibi.
Peter Cordes

11

Python 2,70 bayt

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

Bu işlem dizüstü bilgisayarımda 18 dakika ve 31 saniyede çalıştı ve ardından doğru 1000 rakam üretildi 74100118580(ardından doğru rakamlar geliyor 74248787892).


Kaba kuvvet ve iş tasarrufu güzel karışımı.
Peter Cordes

Bu oldukça basit bir kaba kuvvet yaklaşımının işe yaradığını gösterdiğinden, bunu x86 makine kodunda uygulamayı düşünüyordum. Muhtemelen 100 ila 200 bayt arasında çalışarak, tüm genişletilmiş hassasiyetli işleri elbette manuel olarak yapabiliyordum, ancak özellikle golf oynamak + optimize etmek için önemli bir gelişme zaman alacaktır. Planım 32 bit base10 ** 9'luk parçalardı, bu yüzden 1006 basamağa kadar kesmek ve isteğe bağlı olarak hassas bir bölünme olmadan ondalık bir dizgeye dönüştürmek kolaydır. divÖbek başına 9 ondalık basamak yapmak için sadece bir döngü. ADC yerine cmp / cmov ve 2xADD eklerinde taşıma yapın.
Peter Cordes

Önceki yorumu yazmam için yeterince düşünmek beni kandırdı. Ben bitti x86 32 bit makine kodunu 106 bayt uygulamadan kadar hızlı değildir 10'a bölerek zamanının çoğunu geçirdiği bu piton sürümü (benim masaüstünde bilgisayarım vs 12min35s üzerinde 1min13s çalışan, bu fikri kullanarak genişletilmiş hassas base2 numaraları için!)
Peter Cordes

10

Haskell , 78 bayt

(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

Çevrimiçi deneyin!

TIO'da 48 saniye sürdü. Python cevabımla aynı özyinelemeli formül , ama kesmeden.

Sabit 2143923439, 10**9-1ikili olarak ters çevrilir ve sonunda bir ekstra 1 olur. İkili basamaklarını tersten yineleme, 'in ikilik rakamlarını yineleme 10**9-1. Bunu kodlamak, hesaplamaktan daha kısa görünüyor.


9

Haskell , 202 184 174 173 170 168 164 162 bayt

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

Çevrimiçi deneyin!

açıklama

Bu, fibonacci sayılarını hesaplamak için oldukça hızlı bir yol kullanır. Fonksiyonu lise, iki Fibonacci numaraları alır ve Fibonacci numaraları 10, daha sonra hesaplar falır , n th ve n + 1 inci Fibonacci numaraları ve hesaplar 2n + ı 20 inci ve 2n + 21 inci Fibonacci sayıları. Onları 1 milyar almak ve ilk 1000 rakamı almak için şanssız zincirlerim.


Kendini n kere fonksiyon yapan bir operatör uygulayarak bazı baytları kaydedebilirsiniz.
user1502040

user1502040 yani Kilise rakamları.
Florian F,

8

Haskell, 81 bayt

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

açıklama

f nyinelemeli hesaplar nortak alt ifade eliminasyonu ile XNOR cevabı gelen tekrarını kullanılarak inci Fibonacci sayı. O (log (n)) çarpımlarını kullanan, yayınlanan diğer çözümlerin aksine, O (n) çarpımlarının karmaşıklığı için O (log (n)) - 2 dallanma faktörüne sahip bir derinlik özyinelemesine sahibiz.

Ancak, hepsi kayıp değil! Neredeyse tüm çağrılar özyineleme ağacının dibine yakın olacağından, mümkün olduğunda hızlı doğal aritmetik kullanabilir ve çok büyük bignumların manipülasyonundan kaçınabiliriz. Kutuma birkaç dakika içinde cevap veriyor.


8

T-SQL, 422 414 453 bayt (Doğrulandı, şimdi yarışıyor!)

EDIT 2 : Değiştirildi , Birkaç bayt kazandı, ancak 1 milyarı tamamlayacak kadar hız arttı! 45 saat 29 dakika içinde tamamlanan , verilen dizgiye karşı olduğunu doğrular ve ek 8 karakter görüntüler (yuvarlama hataları nedeniyle doğru olabilir veya olmayabilir).INT BIGINT DECIMAL(37,0)

T-SQL'in yerel "çok sayı" desteği yok, bu yüzden 1008 karakterli dizgiler kullanarak kendi metin tabanlı büyük sayı ekleyicimi döndürmek zorunda kaldım:

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 @

İşte yorumlarla biçimlendirilmiş versiyon:

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 @

Temelde el benim iki Fibonacci değişkenleri temsil eden 1008 karakterlik sıfır dolu dizeleri işleme, kulüpler @ave @.

Onlara eklemek 8 18 (yönetilebilir bir sayısal türe dönüştürerek, son 36 basamağı sıyrılması suretiyle, bir anda 36 basamak DECIMAL(37,0)başka uzunluğunda diziye geri çökertilmesi, sonra onları ekleyerek) @c. Sonra "döndürürüm" @ave @son 36 haneyi öne doğru hareket ettirip işlemi tekrarlayarak. 28 dönüş * 36 hane tüm 1008'i kapsar. "Birini el ile taşımalıyım".

Numaramız sicim uzunluğumu aşmaya başladığında, "sola kayıyorum" ve bazı hassasiyetleri yitirmeye başlıyoruz, ancak hata benim fazladan karakterlerimin içinde.

Ben benzer mantıkla, IntS ve BIGINTs dolu bir SQL tablosu kullanılarak denedim ve oldu dramatik yavaş. Tuhaf.


7
Şirket kaynaklarının etkileyici şekilde kötüye kullanılması!
davidbak

6

PARI / GP, 45 bayt

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

Her nasılsa \p1000yeterli değil. Bu, 32 bit sistemlerle çalışmaz. Son bölüm, bilimsel gösterimde ondalık noktadan kaçınmaktır.


4

Pari / GP , 15 + 5 = 20 bayt

fibonacci(10^9)

-s1g1 Gbyte bellek ayırmak için komut satırı seçeneğiyle çalıştırın .


1

Ruby, 63 bayt

dostum, golf yakutunda kötüyüm; ancak BigInt sınıfı bu tür şeyler için harikalar yaratıyor. Anders Kaseorg ile aynı algoritmayı kullanıyoruz.

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

Bu gerçekten sana bin basamak kazandırıyor mu?
dfeuer
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.