Fibonacci estremi


47

Ci sono state un miliardo di iterazioni delle sfide di Fibonacci su questo sito Web, quindi ravviviamo le cose con una sfida di Fibonacci di un miliardo di iterazioni!

La tua sfida è quella di produrre le prime 1000 cifre decimali del numero 1.000.000.000 di Fibonacci con un programma il più breve possibile. Questo può facoltativamente essere seguito da qualsiasi output aggiuntivo di tua scelta, incluso ma non limitato al resto delle cifre.

Sto usando la convenzione che fib 0 = 0, fib 1 = 1.

Il programma deve essere abbastanza veloce per eseguirlo e verificarne la correttezza. A tal fine, ecco le prime 1000 cifre:

7952317874554683467829385196197148189255542185234398913453039937343246686182519370050999626136556779332482035723222451226291714456275648259499530612111301255499879639516053459789018700567439946844843034599802419924043753401950114830107234265037841426980398387360784284231996457340782784200767760907777703183185744656536253511502851715963351023990699232595471322670365506482435966586886048627159716916351448788527427435508113909167963907380398242848033980110276370544264285032744364781198451825462130529529633339813483105771370128111851128247136311414208318983802526907917787094802217750859685116363883374847428036737147882079956688807509158372249451437519320162582002000530798309887261257028201907509370554232931107084976854715833585623910450679449120011564762925649144509531904684984417002512086504020779012501356177874199605085558317190905395134468919443313026824813363234190494375599262553025466528838122639433600483849535070647711986769279568548796855207684897741771784375859496425384355879105799

Your program must be fast enough for you to run it and verify its correctness.che dire della memoria?
Stephen,

5
@ guest44851 dice chi? ;)
user1502040

1
Se stavo per ovvio penso che un a+=b;b+=a;loop (forse con Java BigInteger) sia la scelta ovvia, almeno se stai pensando anche alle prestazioni. Un'implementazione ricorsiva mi è sempre sembrata orribilmente inefficiente.
Peter Cordes,

2
Sarei interessato a vederne uno in una lingua che non supporta nativamente numeri enormi!
BradC,

1
@BradC: è quello che stavo pensando anch'io. Ci sono voluti circa 2 giorni per sviluppare, eseguire il debug, ottimizzare e giocare a golf, ma ora la mia risposta al codice macchina a 32 bit x86 è pronta (106 byte, inclusa la conversione in una stringa e l'esecuzione di una write()chiamata di sistema). Mi piacciono i requisiti di prestazione, che mi hanno reso molto più divertente.
Peter Cordes,

Risposte:


34

Python 2 + sympy, 72 byte

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

Provalo online!

-10 byte rimuovendo il termine praticamente-0 grazie a Jeff Dege
-1 byte (1000 -> 1e3 grazie a Zacharý)
-2 byte rimuovendo la variabile non necessaria grazie a Erik l'Outgolfer
-2 byte passando a Python 2 grazie a Zacharý
-3 byte di 11 ' -11grazie a ThePirateBay -3 byte sostituendo i strbacktick grazie a notjagan

ora batte la soluzione haskell non pubblicata di OP!


@JonathanAllan Ho notato che from sympy import*;sqrtnon salva byte oltre import sympy;sympy.sqrt:)
HyperNeutrino

Wow, questo è veloce, come funziona?
Kritixi Lithos,

Penso che questo usi l'approssimazione esponenziale per i numeri di fibonacchi e tragga profitto dal dettaglio che sono necessarie solo le prime cifre e3, perché ciò elimina automaticamente qualsiasi problema con una deviazione dall'approssimazione. È corretto?
Fabian Röling,

@Fabian sympyè un pacchetto matematico simbolico per Python, quindi non ci sono problemi con errori di arrotondamento, almeno fino a numeri molto grandi (questo numero non è abbastanza grande lol). Quindi lo computo per darmi le prime cifre 1e3 perché altrimenti se rimuovi la .evalf(1e3)parte, mi dà una rappresentazione della notazione scientifica molto breve.
HyperNeutrino,

1
Dato che sympy non fa parte della libreria standard di python, questa risposta non sembra valida a meno che tu non includa la fonte di sympy nel conteggio dei tuoi personaggi ... o sto completamente fraintendendo le regole del golf del codice?
thegreatemu,

28

Python 2 , 106 byte

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

Provalo online!

Nessuna libreria, solo aritmetica intera. Funziona quasi all'istante.

Il nucleo è l'identità di divisione e conquista:

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

Questo ci consente di aggiornare (a,b) = (f(n),f(n+1))al doppio n -> 2*n. Dal momento che vogliamo ottenere n=10**9, questo richiede solo log_2(10**9)=30iterazioni. Costruiamo nfino a 10**9ripetutamente facendo n->2*n+cper ogni cifra cdella sua espansione binario. Quando c==1, il valore raddoppiato viene spostato verso l'alto 2*n -> 2*n+1con uno spostamento di Fibonacci in una fase(a,b)=(b+a,b)

Per mantenere i valori a,bgestibili, memorizziamo solo le loro prime 1006cifre dividendo il pavimento 10fino a quando non sono sotto 2**3340 ~ 1e1006.


:sul ghiaccio! non usa librerie di fantasia premade lol. : D
HyperNeutrino,

Avevo trovato un più piacevole, ma meno Golfy recidiva, a,b,c=a*a+b*b,a*a-c*c,b*b+c*c.
Neil,

21

codice macchina x86 a 32 bit (con chiamate di sistema Linux): 106 105 byte

log delle modifiche: salvato un byte nella versione veloce perché una costante off-by-one non modifica il risultato per Fib (1G).

O 102 byte per una versione più lenta del 18% (su Skylake) (usando mov/ sub/ cmcinvece di lea/ cmpnel ciclo interno, per generare carry-out e wrapping 10**9invece di 2**32). O 101 byte per una versione più lenta ~ 5,3x con un ramo nel carry-handling nel ciclo più interno. (Ho misurato un tasso di violazione del ramo del 25,4%!)

Oppure 104/101 byte se è consentito uno zero iniziale. (Ci vuole 1 byte in più per hard-code saltando 1 cifra dell'output, che è ciò che è necessario per Fib (10 ** 9)).

Sfortunatamente, la modalità NASM di TIO sembra ignorare -felf32nei flag del compilatore. Ecco comunque un link con il mio codice sorgente completo, con tutta la confusione di idee sperimentali nei commenti.

Questo è un programma completo . Stampa le prime 1000 cifre di Fib (10 ** 9) seguite da alcune cifre extra (le ultime alcune sono errate) seguite da alcuni byte di immondizia (esclusa una nuova riga). La maggior parte della spazzatura è non ASCII, quindi potresti voler passare attraverso cat -v. konsoleTuttavia, non rompe il mio emulatore di terminale (KDE ). I "byte di immondizia" stanno memorizzando Fib (999999999). Avevo già -1024un registro, quindi era più economico stampare 1024 byte della dimensione corretta.

Sto contando solo il codice macchina (dimensione del segmento di testo del mio eseguibile statico), non la lanugine che lo rende un eseguibile ELF. ( Sono possibili eseguibili ELF molto piccoli , ma non volevo preoccuparmene). Si è rivelato più breve utilizzare la memoria dello stack anziché BSS, quindi posso giustificare di non contare nient'altro nel binario poiché non dipendo da alcun metadata. (La produzione normale di un binario statico spogliato rende eseguibile un ELF a 340 byte.)

È possibile ricavare una funzione da questo codice che è possibile chiamare da C. Il salvataggio e il ripristino del puntatore dello stack (forse in un registro MMX) e qualche altro overhead costerebbe qualche byte, ma anche salvare i byte ritornando con la stringa in memoria, invece di effettuare una write(1,buf,len)chiamata di sistema. Penso che giocare a golf nel codice della macchina dovrebbe farmi perdere un po 'di tempo qui, dal momento che nessun altro ha nemmeno pubblicato una risposta in nessuna lingua senza precisione estesa nativa, ma penso che una versione di funzione di questo dovrebbe essere ancora sotto 120 byte senza ricominciare a giocare a golf cosa.


Algoritmo:

forza bruta a+=b; swap(a,b), troncando secondo necessità per mantenere solo le cifre decimali iniziali> = 1017. Funziona in 1min13s sul mio computer (o 322,47 miliardi di cicli di clock + - 0,05%) (e potrebbe essere un po 'più veloce con qualche byte in più di dimensione del codice, o fino a 62s con una dimensione del codice molto più grande dallo srotolamento del loop. No matematica intelligente, sto facendo lo stesso lavoro con meno spese generali). Si basa sull'implementazione Python di @ AndersKaseorg , che funziona in 12min35s sul mio computer (Skylake i7-6700k a 4,4 GHz). Nessuna versione ha alcun errore nella cache L1D, quindi il mio DDR4-2666 non ha importanza.

A differenza di Python, memorizzo i numeri di precisione estesa in un formato che rende libero il troncamento delle cifre decimali . Memorizzo gruppi di 9 cifre decimali per intero a 32 bit, quindi un offset del puntatore scarta le 9 cifre basse. Questo è in realtà 1 miliardo di base, che è una potenza di 10. (È pura coincidenza che questa sfida abbia bisogno del miliardesimo numero di Fibonacci, ma mi fa risparmiare un paio di byte contro due costanti separate.)

Seguendo la terminologia GMP , ogni blocco a 32 bit di un numero a precisione estesa viene chiamato "arto". L'esecuzione durante l'aggiunta deve essere generata manualmente con un confronto rispetto a 1e9, ma viene quindi utilizzata normalmente come input per le normali ADCistruzioni dell'arto successivo. (Devo anche avvolgere manualmente l' [0..999999999]intervallo, piuttosto che a 2 ^ 32 ~ = 4.295e9. Lo faccio senza diramazioni con lea+ cmov, usando il risultato del confronto dal confronto.)

Quando l'ultimo arto produce un risultato diverso da zero, le successive due iterazioni del loop esterno vengono lette da 1 arto più in alto del normale, ma continuano a scrivere nello stesso punto. Questo è come fare uno memcpy(a, a+4, 114*4)spostamento a destra di 1 arto, ma fatto come parte dei successivi due cicli di addizione. Questo accade ogni ~ 18 iterazioni.


Hacks per il risparmio di dimensioni e prestazioni:

  • Le solite cose come lea ebx, [eax-4 + 1]invece di mov ebx, 1, quando lo so eax=4. E l'utilizzo loopin luoghi in cui LOOPla lentezza ha solo un impatto minimo.

  • Troncare gratuitamente di 1 arto compensando i puntatori da cui leggiamo, mentre si scrive ancora all'inizio del buffer nel adcloop interno. Leggiamo [edi+edx]e scriviamo a [edi]. Quindi possiamo ottenere edx=0o 4ottenere un offset di lettura-scrittura per la destinazione. Dobbiamo farlo per 2 iterazioni successive, prima compensando entrambe, poi solo compensando la dst. Rileviamo il secondo caso osservando esp&4prima di reimpostare i puntatori nella parte anteriore dei buffer (utilizzando &= -1024, poiché i buffer sono allineati). Vedi commenti nel codice.

  • L'ambiente di avvio del processo Linux (per un eseguibile statico) azzera la maggior parte dei registri e la memoria dello stack sotto esp/ rspviene azzerata. Il mio programma ne approfitta. In una versione con funzione callable di questo (dove lo stack non allocato potrebbe essere sporco), potrei usare BSS per memoria azzerata (al costo di forse altri 4 byte per impostare i puntatori). L'azzeramento edxrichiederebbe 2 byte. L'ABI x86-64 System V non garantisce nessuno di questi, ma l'implementazione di Linux ne fa zero (per evitare perdite di informazioni dal kernel). In un processo collegato dinamicamente, /lib/ld.soviene eseguito prima _starte lascia registri diversi da zero (e probabilmente immondizia in memoria sotto il puntatore dello stack).

  • Continuo -1024a ebxusarlo al di fuori dei loop. Utilizzare blcome contatore per i loop interni, che termina con zero (che è il byte basso di -1024, ripristinando così la costante per l'uso all'esterno del loop). Intel Haswell e versioni successive non hanno penalità di fusione dei registri parziali per i registri low8 (e in effetti non li rinominano nemmeno separatamente) , quindi c'è una dipendenza dal registro completo, come su AMD (non è un problema qui). Ciò sarebbe orribile su Nehalem e precedenti, tuttavia, che hanno stalle a registro parziale durante la fusione. Ci sono altri posti in cui scrivo registri parziali e poi leggo il reg completo senza xor-zeroing o amovzx, di solito perché so che qualche codice precedente ha azzerato i byte superiori, e di nuovo va bene su AMD e Intel SnB-family, ma lento su Intel pre-Sandybridge.

    Uso 1024il numero di byte per scrivere su stdout ( sub edx, ebx), quindi il mio programma stampa alcuni byte di immondizia dopo le cifre di Fibonacci, perché mov edx, 1000costa più byte.

  • (non utilizzato) adc ebx,ebxcon EBX = 0 per ottenere EBX = CF, risparmiando 1 byte vs. setc bl.

  • dec/ jnzall'interno di un adcloop conserva CF senza causare uno stallo a flag parziale quando adclegge flag su Intel Sandybridge e versioni successive. È negativo per le CPU precedenti , ma AFAIK è gratuito su Skylake. O nel peggiore dei casi, un extra in più.

  • Usa la memoria sotto espcome una gigantesca zona rossa . Poiché si tratta di un programma Linux completo, so di non aver installato alcun gestore di segnale e che nient'altro ostruirà in modo asincrono la memoria dello stack dello spazio utente. Questo potrebbe non essere il caso di altri sistemi operativi.

  • Approfitta dello stack-engine per risparmiare la massima larghezza di banda utilizzando pop eax(1 uop + occasionali stack-sync uop) anziché lodsd(2 uops su Haswell / Skylake, 3 su IvB e precedenti secondo le tabelle di istruzioni di Agner Fog )). IIRC, questo ha ridotto il tempo di esecuzione da circa 83 secondi a 73. Probabilmente avrei potuto ottenere la stessa velocità dall'uso di un movcon una modalità di indirizzamento indicizzato, come mov eax, [edi+ebp]dove ebptiene l'offset tra i buffer src e dst. (Renderebbe il codice esterno al ciclo interno più complesso, dovendo negare il registro offset come parte dello scambio di src e dst per le iterazioni di Fibonacci.) Vedi la sezione "performance" di seguito.

  • avviare la sequenza assegnando alla prima iterazione un carry-in (un byte stc), anziché archiviare un 1in memoria ovunque. Molte altre cose specifiche del problema documentate nei commenti.

Elenco NASM (codice macchina + sorgente) , generato con nasm -felf32 fibonacci-1G.asm -l /dev/stdout | cut -b -28,$((28+12))- | sed 's/^/ /'. (Quindi ho rimosso a mano alcuni blocchi di elementi commentati, quindi la numerazione delle righe presenta delle lacune.) Per eliminare le colonne iniziali in modo da poterle inserire in YASM o NASM, utilizzare 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

Probabilmente c'è spazio per giocare a golf un po 'più di byte, ma ho già trascorso almeno 12 ore su questo in 2 giorni. Non voglio sacrificare la velocità, anche se è molto più che abbastanza veloce e c'è spazio per ridurla in modi che costano la velocità . Parte della mia ragione per la pubblicazione è mostrare quanto velocemente posso fare una versione asm di forza bruta. Se qualcuno vuole davvero andare per dimensioni minime ma forse 10 volte più lento (ad esempio 1 cifra per byte), sentiti libero di copiarlo come punto di partenza.

L'eseguibile risultante (da yasm -felf32 -Worphan-labels -gdwarf2 fibonacci-1G.asm && ld -melf_i386 -o fibonacci-1G fibonacci-1G.o) è 340B (eliminato):

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

Prestazione

Il adcciclo interno è di 10 uops di dominio fuso su Skylake (+1 stack-sync ogni ~ 128 byte), quindi può emettere uno per ~ 2,5 cicli su Skylake con throughput front-end ottimale (ignorando gli stack-sync uops) . La latenza del percorso critico è di 2 cicli, per la catena di dipendenze trasportata in loop dall'iterazione successiva adc-> cmp-> adc, quindi il collo di bottiglia dovrebbe essere il limite di emissione front-end di ~ 2,5 cicli per iterazione.

adc eax, [edi + edx]è 2 uops di dominio non utilizzati per le porte di esecuzione: load + ALU. Si micro-fonde nei decodificatori (1 dominio di tipo fuso), ma non laminati nella fase di emissione a 2 circuiti di dominio fuso, a causa della modalità di indirizzamento indicizzato, anche su Haswell / Skylake . Ho pensato che sarebbe rimasto micro-fuso, come add eax, [edi + edx]fa, ma forse mantenendo le modalità di indirizzamento indicizzato micro-fuso non funziona per gli utenti che hanno già 3 ingressi (flag, memoria e destinazione). Quando l'ho scritto, pensavo che non avrebbe avuto un rovescio della performance, ma mi sbagliavo. Questo modo di gestire il troncamento rallenta ogni volta il ciclo interno, sia che edxsia 0 o 4.

Sarebbe più veloce gestire l'offset di lettura-scrittura per il dst compensando edie usando edxper regolare il negozio. Quindi adc eax, [edi]/ ... / mov [edi+edx], eax/ lea edi, [edi+4]invece di stosd. Haswell e versioni successive possono mantenere un negozio indicizzato microfuso. (Anche Sandybridge / IvB lo svelerebbe.)

Su Intel Haswell e precedenti, adce cmovcsono 2 uops ciascuno, con latenza 2c . (non adc eax, [edi+edx]è ancora laminato su Haswell, ed emette 3 uops di dominio fuso). Broadwell e successivi consentono uops a 3 input per più di un semplice FMA (Haswell), rendendo adce cmovc(e un paio di altre cose) istruzioni single-uop, come se fossero su AMD da molto tempo. (Questo è uno dei motivi per cui AMD ha fatto bene nei benchmark GMP di precisione estesa per lungo tempo.) Comunque, il ciclo interno di Haswell dovrebbe essere di 12 uops (+1 stack-sync di tanto in tanto occasionalmente), con un collo di bottiglia del front-end di ~ 3c per iter nel migliore dei casi, ignorando uops di sincronizzazione dello stack.

L'uso popsenza bilanciamento pushall'interno di un loop significa che il loop non può essere eseguito dall'LSD (loop stream detector) e deve essere riletto dalla cache uop nell'IDQ ogni volta. Semmai, è una buona cosa su Skylake, dal momento che un ciclo di 9 o 10 uop ​​non emette in modo ottimale a 4 uop ad ogni ciclo . Questo è probabilmente parte del motivo per cui la sostituzione lodsdcon ha popaiutato tanto. (L'LSD non può bloccare gli uops perché ciò non lascerebbe spazio per inserire uno stack-sync uop .) (A proposito, un aggiornamento del microcodice disabilita completamente l'LSD su Skylake e Skylake-X per correggere un errore. Ho misurato il sopra prima di ottenere quell'aggiornamento.)

L'ho profilato su Haswell e ho scoperto che funziona con 381,31 miliardi di cicli di clock (indipendentemente dalla frequenza della CPU, poiché utilizza solo cache L1D, non memoria). La velocità di emissione del front-end è stata di 3,72 uops di dominio fuso per clock, rispetto a 3,70 per Skylake. (Ma ovviamente le istruzioni per ciclo erano scese a 2,42 da 2,87, perché adce cmovsono 2 uops su Haswell.)

pushsostituire stosdprobabilmente non sarebbe di grande aiuto, perché adc [esp + edx]ogni volta si innescerebbe uno stack-sync. E costerebbe un byte per stdcosì lodsdva nella direzione opposta. ( mov [edi], eax/ lea edi, [edi+4]sostituire stosdè una vittoria, passando da 32.909 cicli per 100 milioni di iter a 31.954 cicli per 100 milioni di iter. Sembra che stosddecodifichi come 3 uops, con gli indirizzi di negozio / dati di negozio non micro-fusi, quindi push+ stack-sync uops potrebbe essere ancora più veloce di stosd)

Le prestazioni effettive di ~ 322,47 miliardi di cicli per iterazioni 1G di 114 arti raggiungono i 2.824 cicli per iterazione del loop interno , per la veloce versione 105B su Skylake. (Vedi l' ocperf.pyoutput di seguito). È più lento di quanto avevo previsto dall'analisi statica, ma stavo ignorando il sovraccarico del ciclo esterno e tutti i cicli di sincronizzazione dello stack.

Perf contrasta branchese branch-missesmostra che il ciclo interno fornisce una previsione errata una volta per ciclo esterno (nell'ultima iterazione, quando non viene preso). Ciò rappresenta anche una parte dei tempi supplementari.


Ho potuto risparmiare codice-size rendendo più interna anello una latenza 3 cicli per il percorso critico, utilizzando mov esi,eax/ sub eax,ebp/ cmovc eax, esi/cmc (2 + 2 + 3 + 1 = 8B) anziché lea esi, [eax - 1000000000]/ cmp ebp,eax/ cmovc(6 + 2 + 3 = 11B ). Il cmov/ stosdè fuori dal percorso critico. (L'editor incrementale di stosdpuò essere eseguito separatamente dall'archivio, quindi ogni iterazione si stacca da una breve catena di dipendenze.) Ha usato per salvare un altro 1B modificando l'istruzione init di ebp da lea ebp, [ecx-1]a mov ebp,eax, ma ho scoperto che avere l'erroreebpnon ha cambiato il risultato. Ciò consentirebbe a un arto di essere esattamente == 1000000000 invece di avvolgere e produrre un carry, ma questo errore si propaga più lentamente della crescita di Fib (), quindi ciò non modifica le cifre 1k iniziali del risultato finale. Inoltre, penso che l'errore possa correggersi quando stiamo solo aggiungendo, poiché c'è spazio in un arto per trattenerlo senza trabocco. Perfino 1G + 1G non trabocca di un numero intero a 32 bit, quindi alla fine percolerà verso l'alto o verrà troncato.

La versione di latenza 3c è 1 extra in più, quindi il front-end può emetterlo a uno per 2,75c cicli su Skylake, solo leggermente più veloce del back-end può eseguirlo. (Su Haswell, sarà 13 uops in totale poiché utilizza ancora adce cmov, e collo di bottiglia sul front-end a 3,25 c per iter).

In pratica corre un fattore di 1,18 più lento su Skylake (3,34 cicli per arto), piuttosto che 3 / 2,5 = 1,2 che avevo predetto per sostituire il collo di bottiglia del front-end con il collo di bottiglia della latenza dal solo guardare il ciclo interno senza stack-sync UOP. Dato che lo stack-sync uops danneggia solo la versione veloce (collo di bottiglia sul front-end anziché latenza), non ci vuole molto per spiegarlo. ad es. 3 / 2.54 = 1.18.

Un altro fattore è che la versione di latenza 3c può rilevare il colpevole di lasciare il ciclo interno mentre il percorso critico è ancora in esecuzione (perché il front-end può andare avanti rispetto al back-end, lasciando che l'esecuzione fuori servizio esegua il loop- contromisure), quindi la penalità per errore effettivo è inferiore. Perdere quei cicli front-end consente al back-end di recuperare.

Se non fosse per quello, potremmo forse accelerare la cmcversione 3c usando un ramo nel ciclo esterno invece della gestione senza rami degli offset carry_out -> edx ed esp. Branch-prediction + esecuzione speculativa per una dipendenza di controllo anziché una dipendenza di dati potrebbe consentire alla successiva iterazione di iniziare a eseguire il adcciclo mentre i loop del precedente ciclo interno erano ancora in volo. Nella versione senza rami, gli indirizzi di carico nel loop interno hanno una dipendenza dati da CF dall'ultimo adcdell'ultimo arto.

Il collo di bottiglia della versione a ciclo interno di latenza 2c è presente sul front-end, quindi il back-end praticamente mantiene il passo. Se il codice del ciclo esterno fosse ad alta latenza, il front-end potrebbe andare avanti emettendo uops dalla successiva iterazione del ciclo interno. (Ma in questo caso le cose del ciclo esterno hanno un sacco di ILP e nessuna roba ad alta latenza, quindi il back-end non ha molto da recuperare quando inizia a masticare attraverso il ciclo nello schedulatore fuori servizio come i loro input diventano pronti).

### 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 %)è la deviazione standard sulle 4 corse per quel conteggio. Interessante che esegua un numero così tondo di istruzioni. Quel 924 miliardi non è una coincidenza. Suppongo che il ciclo esterno esegua un totale di 924 istruzioni.

uops_issuedè un conteggio di domini fusi (rilevante per la larghezza di banda dei problemi di front-end), mentre uops_executedè un conteggio di domini non fusi (numero di uops inviati alle porte di esecuzione). La micro-fusione racchiude 2 Uops di dominio non fuso in un solo UOP di dominio fuso, ma l' eliminazione di mov significa che alcuni UOP di dominio fuso non necessitano di porte di esecuzione. Vedi la domanda collegata per ulteriori informazioni sul conteggio dei domini uops e fused vs. unfused. (Vedi anche le tabelle di istruzioni di Agner Fog e la guida di Uarch e altri link utili nel wiki SO x86 del tag ).

Da un'altra corsa, misurando cose diverse: i mancati cache L1D sono totalmente insignificanti, come previsto per la lettura / scrittura degli stessi due buffer 456B. Il ramo del ciclo interno indica erroneamente una volta per ciclo esterno (quando non viene preso per lasciare il ciclo). (Il tempo totale è maggiore perché il computer non era completamente inattivo. Probabilmente l'altro core logico era attivo per un po 'di tempo e più tempo è stato impiegato in interruzioni (poiché la frequenza misurata dallo spazio utente era più lontana sotto i 4.400 GHz). O più core erano attivi per la maggior parte del tempo, riducendo il turbo massimo. Non ho tracciato cpu_clk_unhalted.one_thread_activeper vedere se la competizione HT fosse un problema.)

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

Il mio codice potrebbe essere eseguito in meno cicli su Ryzen, che può emettere 5 uops per ciclo (o 6 quando alcuni di essi sono istruzioni 2-uop, come roba AVX 256b su Ryzen). Non sono sicuro di cosa farebbe il suo front-end stosd, ovvero 3 uops su Ryzen (uguale a Intel). Penso che le altre istruzioni nel ciclo interno abbiano la stessa latenza di Skylake e tutte le single-uop. (Compreso adc eax, [edi+edx], che è un vantaggio rispetto a Skylake).


Questo potrebbe probabilmente essere significativamente più piccolo, ma forse 9 volte più lento, se memorizzassi i numeri come 1 cifra decimale per byte . Generare il carry-out cmpe adattarsi con cmovfunzionerebbe allo stesso modo, ma farebbe 1/9 del lavoro. Funzionerebbero anche 2 cifre decimali per byte (base-100, non BCD a 4 bit con un lentoDAA ) e div r8/ / add ax, 0x3030trasforma uno 0-99 byte in due cifre ASCII in ordine di stampa. Ma 1 cifra per byte non è affatto necessaria div, basta un loop e l'aggiunta di 0x30. Se conservo i byte in ordine di stampa, ciò renderebbe il 2 ° ciclo davvero semplice.


L'uso di 18 o 19 cifre decimali per numero intero a 64 bit (in modalità 64 bit) lo farebbe funzionare circa due volte più veloce, ma costerebbe dimensioni significative del codice per tutti i prefissi REX e per le costanti a 64 bit. Arti a 32 bit in modalità 64 bit ne impediscono l'utilizzo pop eaxanziché lodsd. Potrei ancora evitare i prefissi REX usando espcome un registro scratch senza puntatore (scambiando l'uso di esie esp), invece di usare r8dcome un ottavo registro.

Se si effettua una versione con funzione richiamabile, la conversione in 64 bit e l'utilizzo r8dpotrebbero essere più economici del salvataggio / ripristino rsp. Inoltre, a 64 bit non è possibile utilizzare la dec r32codifica a un byte (poiché si tratta di un prefisso REX). Ma per lo più ho finito con l'utilizzo di dec bl2 byte. (Perché ho una costante nei byte superiori di ebxe la uso solo al di fuori dei loop interni, il che funziona perché il byte basso della costante è 0x00.)


Versione ad alte prestazioni

Per ottenere le massime prestazioni (non il golf di codice), vorrai srotolare il ciclo interno in modo che esegua al massimo 22 iterazioni, che è un modello abbastanza breve preso / non preso per i predittori di ramo per fare bene. Nei miei esperimenti, mov cl, 22prima che un .inner: dec cl/jnz .innerloop abbia pochissimi errori (come 0,05%, molto meno di uno per ciclo completo del loop interno), ma mov cl,23fraintendimenti da 0,35 a 0,6 volte per loop interno. 46è particolarmente male, prevedendo erroneamente ~ 1,28 volte per loop interno (128M volte per iterazioni di loop esterno 100M). 114erroneamente previsto una volta per ciclo interno, lo stesso che ho trovato come parte del ciclo di Fibonacci.

Mi sono incuriosito e l'ho provato, srotolando il ciclo interno di 6 con un %rep 6(perché questo divide equamente 114). Ciò ha eliminato per lo più i fallimenti delle filiali. L'ho reso edxnegativo e l' ho usato come offset per i movnegozi, quindi adc eax,[edi]potrei rimanere microfuso. (E così ho potuto evitare stosd). Ho estratto il leaper aggiornare edidal %repblocco, quindi esegue solo un aggiornamento del puntatore per 6 negozi.

Mi sono anche sbarazzato di tutte le cose del registro parziale nel loop esterno, anche se non penso che sia stato significativo. Potrebbe aver aiutato leggermente avere CF alla fine del loop esterno non dipendente dall'ADC finale, quindi alcuni dei loop del ciclo interno possono iniziare. Probabilmente il codice del loop esterno potrebbe essere ottimizzato un po 'di più, poiché è neg edxstata l'ultima cosa che ho fatto, dopo averlo sostituito xchgcon solo 2 movistruzioni (dato che ne avevo ancora 1), e riordinare le catene dep insieme a far cadere l'8-bit registra cose.

Questa è la fonte NASM del solo ciclo di Fibonacci. È un sostituto drop-in per quella sezione della versione originale.

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

Prestazione:

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

Questo è per lo stesso Fib (1G), producendo la stessa uscita in 62,3 secondi anziché 73 secondi. (273.146G cicli, contro 322.467G. Poiché tutto colpisce nella cache L1, i cicli di core clock sono davvero tutto ciò che dobbiamo guardare.)

Nota il uops_issuedconteggio totale molto più basso , ben al di sotto del uops_executedconteggio. Ciò significa che molti di loro erano micro-fusi: 1 uop nel dominio fuso (problema / ROB), ma 2 uop nel dominio non fuso (scheduler / unità di esecuzione)). E che pochi sono stati eliminati nella fase di emissione / ridenominazione (come la movcopia del registro o xor-zeroing, che devono essere emessi ma non necessitano di un'unità di esecuzione). Gli uops eliminati sbilancerebbero il conteggio nell'altro modo.

branch-missesscende a ~ 400k, da 1G, quindi lo srotolamento ha funzionato. resource_stalls.anyè significativo ora, il che significa che il front-end non è più il collo di bottiglia: invece il back-end si sta allontanando e limitando il front-end. idq_uops_not_delivered.coreconta solo i cicli in cui il front-end non ha erogato uops, ma il back-end non è stato bloccato. È bello e basso, indicando alcuni colli di bottiglia front-end.


Curiosità: la versione di Python impiega più della metà del tempo a dividere per 10 invece di aggiungere. (La sostituzione di a/=10con lo a>>=64accelera di oltre un fattore 2, ma modifica il risultato a causa del troncamento binario! = Troncamento decimale.)

La mia versione asm è ovviamente ottimizzata in modo specifico per questa dimensione del problema, con l'iterazione del ciclo che conta hard coded. Anche lo spostamento di un numero di precisione arbitraria lo copierà, ma la mia versione può semplicemente leggere da un offset per le successive due iterazioni per saltare anche quello.

Ho profilato la versione di Python (64-bit python2.7 su Arch Linux):

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

I numeri in (parentesi) indicano per quanto tempo è stato campionato il perf counter. Quando si osservano più contatori di quelli supportati da HW, perf ruota tra diversi contatori ed estrapolati. Va benissimo per un lungo periodo dello stesso compito.

Se avessi eseguito perfdopo aver impostato sysctl kernel.perf_event_paranoid = 0(o in esecuzione perfcome root), avrebbe misurato 4.400GHz. cycles:unon conta il tempo trascorso in interrupt (o chiamate di sistema), ma solo cicli spazio utente. Il mio desktop era quasi completamente inattivo, ma questo è tipico.


20

Haskell, 83 61 byte

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)

Uscite ( F 1000000000 , F 1000000001 ). Sul mio computer portatile, stampa correttamente il paren sinistro e le prime 1000 cifre entro 133 secondi, utilizzando 1,35 GiB di memoria.

Come funziona

La ricorrenza di Fibonacci può essere risolta usando l'espiazione della matrice:

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

da cui deriviamo queste identità:

[ F i + j - 1 , F i + j ; F i + j , F i + j + 1 ] = [ F i - 1 , F i ; F i , F 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 .

La pfunzione calcola ( F i + j , F i + j + 1 ) dato ( F i , F i + 1 ) e ( F j , F j + 1 ). Scrivendo f nper ( F i , F i + 1 ), abbiamo p (f i) (f j)= f (i + j).

Poi,

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

e inseriamo f 1= (1,1).


12

Mathematica, 15 34 byte

Fibonacci stesso prende ~ 6s sul mio computer. E 95 (+/- 5) s per il frontend per visualizzarlo.

Fibonacci@1*^9&

inserisci qui la descrizione dell'immagine

Le prime 1000 cifre (34 byte): ⌊Fibonacci@1*^9/1*^208986640⌋&

test 1

Più a lungo ma più veloce ToString@Fibonacci@1*^9~StringTake~1000&:

screenshot di prova


1
6 secondi ?! Che tipo di computer stai usando? Mi ci sono voluti 140 secondi! (inoltre, ci vuole davvero 10 volte di più per trasformarlo in una stringa e ottenere i primi 1000 caratteri che semplicemente calcolarlo?)
numbermaniac

1
@numbermaniac Spiacente, dovrei chiarire che il frontend impiega molto più tempo a visualizzare il numero.
Keyu Gan,

1
@numbermaniac: Quei tempi non mi sorprendono davvero. Internamente il risultato di Fibonacci è probabilmente in base2 e IIRC che calcola l'ennesimo numero di Fibonacci può essere fatto in operazioni O (log (n)); Mathematica non si fa certo strada attraverso le grandi aggiunte di BigInteger. IDK la lingua così bene; forse sta usando una valutazione parzialmente pigra per evitare di creare un BigInteger da 71,5 MB.
Peter Cordes,

2
@numbermaniac: Ancora più importante, la rappresentazione interna è in base2 e la conversione in una stringa base10 richiede una divisione ripetuta per 10. La divisione intera è molto più lenta della moltiplicazione intera per numeri interi a 64 bit ed è altrettanto dannosa per la precisione estesa a due registri (se non peggio, perché moltiplicare viene pipeline meglio di dividere anche in CPU x86 molto recenti con hardware di divisione abbastanza buono). Presumo che sia altrettanto negativo per la precisione arbitraria, anche per un divisore di piccole costanti come 10.
Peter Cordes,

1
Stavo cercando una risposta del codice macchina x86 a questa domanda e stavo considerando di mantenere i miei numeri decimali per tutto il tempo. Ciò consisteva principalmente nell'accorciare l'implementazione non necessitando affatto di un circuito di divisione con precisione estesa. (Stavo pensando forse con 2 cifre per byte (0..99) o 0..1e9-1 per blocco a 32 bit, quindi ogni blocco si trasforma in un numero costante di cifre decimali e posso semplicemente usarlo div). Mi sono fermato dal momento che la gente avrebbe probabilmente finito di guardare a questa domanda quando avessi avuto una funzione ben giocata che faceva tutto quel lavoro. Ma apparentemente la forza bruta può funzionare, come mostrano alcune risposte.
Peter Cordes,

11

Python 2, 70 byte

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

Questo ha funzionato in 18 minuti e 31 secondi sul mio laptop, producendo le 1000 cifre corrette seguite da 74100118580(le cifre seguenti sono corrette 74248787892).


Bel mix di forza bruta e risparmio di lavoro.
Peter Cordes,

Dal momento che ciò dimostra che un approccio abbastanza semplice alla forza bruta funziona, stavo pensando di implementarlo nel codice macchina x86. Probabilmente riuscirò a farlo funzionare tra 100 e 200 byte, facendo ovviamente tutte le cose di precisione estesa manualmente, ma ci vorrebbe un tempo di sviluppo significativo, in particolare per golfarlo + ottimizzarlo. Il mio piano prevedeva blocchi a 32 bit di base10 ** 9, quindi è facile troncare fino a 1006 cifre e convertire facilmente in una stringa decimale senza divisione di precisione arbitraria. Solo un divciclo per creare 9 cifre decimali per blocco. Trasportare durante le aggiunte con cmp / cmov e 2xADD invece di ADC.
Peter Cordes,

Pensandoci abbastanza per scrivere quel commento precedente mi ha fatto impazzire. Ho finito per implementarlo in 106 byte di codice macchina x86 a 32 bit usando quell'idea, in esecuzione in 1min13s sul mio computer contro 12min35s sul mio desktop per questa versione di Python (che passa la maggior parte del tempo a dividere per 10, che non è veloce per i numeri di base2 di precisione estesa!)
Peter Cordes,

10

Haskell , 78 byte

(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

Provalo online!

Ci sono voluti 48 secondi su TIO. Stessa formula ricorsiva della mia risposta Python , ma senza troncare.

La costante 2143923439è 10**9-1, invertita in binario, e con un ulteriore 1 alla fine. L'iterazione attraverso le sue cifre binarie al contrario simula l'iterazione attraverso le cifre binarie di 10**9-1. Sembra più breve codificare questo codice piuttosto che calcolarlo.


9

Haskell , 202 184 174 173 170 168 164 162 byte

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

Provalo online!

Spiegazione

Questo utilizza un modo piuttosto veloce per calcolare i numeri di fibonacci. La funzione lprende due numeri di fibonacci e calcola i numeri di fibonacci 10 in seguito, mentre fprende i numeri nn e n + 1 ° di fibonacci e calcola i numeri 2n + 20 ° e 2n + 21 di fibonacci. Li incateno piuttosto a casaccio per ottenere 1 miliardo e afferrare le prime 1000 cifre.


È possibile salvare alcuni byte implementando un operatore che compone una funzione con se stesso n volte.
user1502040

@ user1502040 ie numeri della Chiesa.
Florian F,

8

Haskell, 81 byte

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

Spiegazione

f ncalcola ricorsivamente il nnumero di fibonacci usando la ricorrenza dalla risposta di xnor con l'eliminazione della sottoespressione comune. A differenza delle altre soluzioni che sono state pubblicate, che usano moltiplicazioni O (log (n)), abbiamo una ricorsione di profondità O (log (n)) con un fattore di ramificazione di 2, per una complessità di moltiplicazioni O (n).

Tuttavia, non tutto è perduto! Poiché quasi tutte le chiamate saranno vicine al fondo dell'albero della ricorsione, possiamo usare l'aritmetica nativa veloce ove possibile ed evitare molta manipolazione di enormi bignum. Sputa una risposta in un paio di minuti sulla mia scatola.


8

T-SQL, 422 414 453 byte (verificato, ora in competizione!)

EDIT 2 : modificato in , ottenuto alcuni byte ma maggiore velocità sufficiente per completare a 1 miliardo! Completato in 45 ore e 29 minuti , verifica rispetto alla stringa specificata e visualizza altri 8 caratteri (che possono essere o meno corretti a causa di errori di arrotondamento).INT BIGINT DECIMAL(37,0)

T-SQL non ha il supporto nativo di "numeri enormi", quindi ho dovuto lanciare il mio sommatore di numeri enormi basato su testo usando stringhe di 1008 caratteri:

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 @

Ecco la versione formattata con commenti:

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 @

Fondamentalmente sto manipolando manualmente stringhe piene di zero di 1008 caratteri che rappresentano le mie due variabili di Fibonacci @ae @.

Le aggiungo 8 18 36 cifre alla volta, togliendo le ultime 36 cifre, convertendole in un tipo numerico gestibile ( DECIMAL(37,0)), aggiungendole e poi fracassandole in un'altra lunga stringa @c. Poi "giro" @ae @spostando le ultime 36 cifre in primo piano e ripetendo il processo. 28 rotazioni * 36 cifre coprono tutte le 1008. Devo "trasportare quella" manualmente.

Quando il nostro numero inizia a superare la lunghezza della mia stringa, "spostamento a sinistra" e iniziamo a perdere un po 'di precisione, ma l'errore è ben compreso nei miei caratteri extra.

Ho provato a utilizzare una tabella SQL piena di INT e BIGINT, con una logica simile, ed è stata notevolmente più lenta. Strano.


7
Impressionante uso improprio delle risorse aziendali!
davidbak,

6

PARI / GP, 45 byte

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

In qualche modo \p1000non è abbastanza. Questo non funziona con i sistemi a 32 bit. La divisione finale consiste nell'evitare il punto decimale nella notazione scientifica.


4

Pari / GP , 15 + 5 = 20 byte

fibonacci(10^9)

Esegui con l'opzione della riga di comando -s1gper allocare 1 Gbyte di memoria.


1

Rubino, 63 byte

amico, sono cattivo con il rubino da golf; ma la classe BigInt fa miracoli per questo tipo di cose. Usiamo lo stesso algoritmo di Anders Kaseorg.

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

Ti dà davvero un migliaio di cifre?
Dfeuer
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.