Крайні Фібоначчі


47

На цьому веб-сайті було проведено мільярд ітерацій викликів з Фібоначчі, тож давайте пришвидшити справи викликом Фібоначчі на мільярд ітерацій!

Ваше завдання полягає в тому, щоб вивести перші 1000 десяткових цифр з числа 1 000 000 000-го числа Фібоначчі з якомога коротшою програмою. Після цього необов'язково може супроводжуватися будь-який додатковий результат на ваш вибір, включаючи, але не обмежуючись ними, цифри.

Я використовую конвенції , що fib 0 = 0, fib 1 = 1.

Ваша програма повинна бути достатньо швидкою, щоб ви її запустили та перевірили її правильність. Для цього ось перші 1000 цифр:

7952317874554683467829385196197148189255542185234398913453039937343246686182519370050999626136556779332482035723222451226291714456275648259499530612111301255499879639516053459789018700567439946844843034599802419924043753401950114830107234265037841426980398387360784284231996457340782784200767760907777703183185744656536253511502851715963351023990699232595471322670365506482435966586886048627159716916351448788527427435508113909167963907380398242848033980110276370544264285032744364781198451825462130529529633339813483105771370128111851128247136311414208318983802526907917787094802217750859685116363883374847428036737147882079956688807509158372249451437519320162582002000530798309887261257028201907509370554232931107084976854715833585623910450679449120011564762925649144509531904684984417002512086504020779012501356177874199605085558317190905395134468919443313026824813363234190494375599262553025466528838122639433600483849535070647711986769279568548796855207684897741771784375859496425384355879105799

Your program must be fast enough for you to run it and verify its correctness.як щодо пам’яті?
Стівен

5
@ guest44851 каже хто? ;)
user1502040

1
Якщо я йшов на очевидний, я думаю, що a+=b;b+=a;цикл (можливо, з Java BigInteger) - це очевидний вибір, принаймні, якщо ви навіть думаєте про продуктивність. Рекурсивна реалізація мені завжди здавалася жахливо неефективною.
Пітер Кордес

2
Мені буде цікаво побачити його мовою, яка не підтримує величезну кількість!
BradC

1
@BradC: Про це я теж думав. На розробку, налагодження, оптимізацію та гольф знадобилося близько 2 днів, але тепер мій 32-розрядний відповідь на машинний код x86 готовий (106 байт, включаючи перетворення на рядок та здійснення write()системного дзвінка). Мені подобається вимога до продуктивності, що зробило це для мене більш веселим.
Пітер Кордес

Відповіді:


34

Python 2 + symy, 72 байти

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

Спробуйте в Інтернеті!

-10 байт, видаливши практично-0 термін завдяки Jeff Dege
-1 байт (1000 -> 1e3 завдяки Zacharý)
-2 байт, видаливши непотрібну змінну завдяки Еріку Outgolfer
-2 байти, перейшовши на Python 2 завдяки Zacharý
-3 байти на 11 ' -11завдяки спасибі ThePirateBay -3 байти, замінивши strна задню панель завдяки notjagan

тепер перемагає неопубліковане рішення haskell OP!


@JonathanAllan Я помітив, що from sympy import*;sqrtбайтів не економить import sympy;sympy.sqrt:)
HyperNeutrino

Вау, це швидко, як це працює?
Kritixi Lithos

Я думаю, що для цього використовується експоненціальне наближення для чисел фібоначчі та отримує прибуток від деталей, що потрібні лише перші цифри e3, оскільки це автоматично усуває будь-яку проблему з відхиленням від наближення. Це правильно?
Фабіан Рьолінг

@Fabian sympyє символічним математичним пакетом для Python, тому проблем з обрізанням помилок немає, принаймні, поки дуже великі числа (це число недостатньо велике лол). Тоді я просто обчислюю це, щоб дати мені перші цифри 1e3, оскільки в іншому випадку, якщо ви вилучите .evalf(1e3)деталь, це дає мені дуже коротке представлення наукових позначень.
HyperNeutrino

1
Оскільки sympy не є частиною стандартної бібліотеки python, ця відповідь не здається дійсною, якщо ви не включите джерело sympy в число персонажів ... або я зовсім неправильно трактую правила гольфу коду?
thegreatemu

28

Python 2 , 106 байт

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

Спробуйте в Інтернеті!

Ніяких бібліотек, а лише ціла арифметика. Працює майже миттєво.

Основою є ідентифікація «ділити-перемагай»:

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

Це дозволяє нам оновити (a,b) = (f(n),f(n+1))вдвічі n -> 2*n. Оскільки ми хочемо отримати n=10**9, це потребує лише log_2(10**9)=30ітерацій. Ми будуємо nце 10**9, повторюючи n->2*n+cдля кожної цифри cйого бінарне розширення. Коли c==1подвоєне значення зміщується вгору 2*n -> 2*n+1з одномоментним зсувом Фібоначчі(a,b)=(b+a,b)

Щоб зберегти a,bкеровані значення , ми зберігаємо лише їхні перші 1006цифри шляхом поділу підлоги до 10тих пір, поки вони не знаходяться нижче 2**3340 ~ 1e1006.


: o приємно! не використовує фантазійні попередньо створені бібліотеки lol : D
HyperNeutrino

Я знайшов більш приємні , але менш golfy рецидивів, a,b,c=a*a+b*b,a*a-c*c,b*b+c*c.
Ніл

21

32-бітний машинний код x86 (з системними дзвінками Linux): 106 105 байт

changelog: збережений байт у швидкій версії, оскільки константа "один за одним" не змінює результат для Fib (1G).

Або 102 байти для 18% повільнішої (у Skylake) версії (використовуючи mov/ sub/ cmcзамість lea/ cmpу внутрішньому циклі, щоб генерувати виконання та обгортання 10**9замість 2**32). Або 101 байт для більш повільної версії ~ 5,3x із гілкою в обробці, що переноситься, у самому циклі. (Я виміряв 25,4% коефіцієнта неправильного прогнозування!)

Або 104/101 байт, якщо дозволений провідний нуль. (Потрібно 1 додатковий байт, щоб пропустити жорсткий код 1 цифру виводу, що саме потрібно для Fib (10 ** 9)).

На жаль, режим NASM TIO, схоже, ігнорує -felf32у прапорах компілятора. Ось посилання все-таки з моїм повним вихідним кодом, з усім безладом експериментальних ідей у ​​коментарях.

Це повна програма . Він друкує перші 1000 цифр Fib (10 ** 9), а потім додаткові цифри (останні кілька з яких неправильні), а потім кілька байтів сміття (не враховуючи новий рядок). Велика частина сміття не є ASCII, так що ви можете труби через cat -v. Хоча це не порушує мій емулятор терміналу (KDE konsole). "Байти сміття" зберігають Fib (999999999). У мене вже був -1024реєстр, тому було дешевше надрукувати 1024 байти, ніж належного розміру.

Я рахую лише машинний код (розмір текстового сегмента мого статичного виконуваного файлу), а не пух, який робить його виконуваним ELF. ( Можливі дуже маленькі виконувані файли ELF , але я не хотів цим заважати). Виявилося, що використання пам'яті стека замість BSS виявилося коротшим, тому я можу виправдати, що не рахую нічого іншого у двійковому, оскільки я не залежую від жодних метаданих. (Нормальний спосіб виготовлення позбавленого статичного двійкового файлу робить виконуваним ELF 340 байт.)

Ви можете зробити функцію з цього коду, на який ви могли б зателефонувати з C. Кошто коштуватиме кілька байтів, щоб зберегти / відновити покажчик стека (можливо, в регістрі MMX) та деякі інші накладні витрати, але також зберегти байти, повернувшись із рядком в пам'яті, замість того, щоб робити write(1,buf,len)системний дзвінок. Я думаю, що гольф у машинному коді повинен заробити мене тут трохи, оскільки ніхто інший навіть не опублікував відповіді будь-якою мовою без рідної розширеної точності, але я вважаю, що версія цієї функції все ж повинна бути менше 120 байт, не переробляючи цілого гольфу. річ.


Алгоритм:

груба сила a+=b; swap(a,b), обрізання за необхідності, щоб зберегти лише провідні> = 1017 десяткових цифр. Він працює на 1min13s на моєму комп’ютері (або 322,47 мільярда тактових циклів + - 0,05%) (і може бути на кілька відсотків швидше з кількома додатковими байтами розміру коду, або до 62-х з набагато більшим розміром коду від розмотування циклу. Ні. розумна математика, просто виконуючи ту саму роботу з меншими накладними витратами). Він заснований на реалізації Python @ AndersKaseorg , яка працює на 12 хвилинах 35х на моєму комп'ютері (4,4 ГГц Skylake i7-6700k). Жодна з версій не має жодного кешу L1D, тому мій DDR4-2666 не має значення.

На відміну від Python, я зберігаю номери з розширеною точністю у форматі, який робить обрізання десяткових цифр безкоштовним . Я зберігаю групи з 9 десяткових цифр на 32-бітне ціле число, тому зміщення покажчика відкидає низькі 9 цифр. Це фактично базовий 1 мільярд, що є потужністю 10. (Чистий збіг обставин, що для цього виклику потрібне 1-мільярдне число Фібоначчі, але це врятує мені пару байтів проти двох окремих констант.)

Дотримуючись термінології GMP , кожен 32-розрядний фрагмент числа з підвищеною точністю називається "кінцівкою". Виконання під час додавання потрібно генерувати вручну з порівнянням проти 1e9, але потім зазвичай використовується як вхід до звичайної ADCінструкції для наступної кінцівки. (Я також повинен вручну завернути до [0..999999999]діапазону, а не на 2 ^ 32 ~ = 4.295e9. Я це роблю без гілок з lea+ cmov, використовуючи результат виконання порівняння.)

Коли остання кінцівка створює ненульовий винос, наступні дві ітерації зовнішньої петлі зчитуються на 1 кінцівку вище, ніж зазвичай, але все одно записують на те саме місце. Це як би зробити memcpy(a, a+4, 114*4)зсув правої частини на 1 кінцівку, але виконано як частину наступних двох циклів додавання. Це відбувається кожні ~ 18 ітерацій.


Хаки для економії розміру та продуктивності:

  • Звичайні речі, як lea ebx, [eax-4 + 1]замість того mov ebx, 1, коли я це знаю eax=4. І використання loopв місцях, де LOOPповільність має лише крихітний вплив.

  • Обрізати на 1 кінцівку безкоштовно, зміщуючи покажчики, з яких ми читаємо, при цьому все ще записуючи до початку буфера у adcвнутрішньому циклі. Ми читаємо з [edi+edx], і пишемо в [edi]. Таким чином, ми можемо отримати edx=0або 4отримати компенсацію читання-запису для місця призначення. Нам потрібно зробити це для двох послідовних ітерацій, спочатку компенсуючи обидві, потім лише компенсуючи даст. Ми виявляємо другий випадок, дивлячись esp&4перед тим, як скинути покажчики на передню частину буферів (використовуючи &= -1024, тому що буфери вирівняні). Дивіться коментарі в коді.

  • Середовище запуску процесів Linux (для статичного виконуваного файлу) більшість реєстрів, а стек-пам'ять нижче esp/ rspнульова. Моя програма цим користується. У версії цієї функції, що вимагає дзвінка (де нерозподілений стек може бути забрудненим), я міг би використовувати BSS для нульової пам'яті (ціною, можливо, ще 4-х байтів, щоб встановити покажчики). Нулювання edxзайняло б 2 байти. Система x86-64 System V ABI не гарантує жодного з них, але реалізація цього Linux робить нульовий (щоб уникнути витоку інформації з ядра). У динамічно пов'язаному процесі він /lib/ld.soзапускається раніше _startі не залишає регістри ненульовими (і, ймовірно, сміття в пам'яті нижче вказівника стека).

  • Я тримаю -1024в ebxдля використання зовні петель. Використовуйте blяк лічильник для внутрішніх циклів, що закінчуються нулем (що є низьким байтом -1024, тим самим відновлюючи константу для використання поза циклом). Intel Haswell і пізніше не мають часткових реєстрів для об'єднання покарань для низьких8 регістрів (а насправді навіть не перейменовують їх окремо) , тому існує залежність від повного реєстру, як у AMD (тут не проблема). Це було б жахливо для Негалема та більш ранніх, однак при злитті вони мають стоянки з частковою реєстрацією. Є й інші місця, де я пишу часткові регістри, а потім читаю повний xorрегістр без -zeroing або amovzx, як правило, тому, що я знаю, що деякий попередній код обнуляв верхні байти, і знову це нормально для AMD та Intel SnB-сімейства, але повільно для Intel-попереднього Sandybridge.

    Я використовую 1024як кількість байтів для запису в stdout ( sub edx, ebx), тому моя програма друкує кілька байтів сміття після цифр Фібоначчі, оскільки mov edx, 1000коштує більше байтів.

  • (Не використовується) adc ebx,ebxз EBX = 0 , щоб отримати EBX = CF, економлячи 1 байт проти setc bl.

  • dec/ jnzвсередині adcциклу зберігає CF, не викликаючи часткового прапора під час adcзчитування прапорів на Intel Sandybridge та пізніших версіях. Це погано на попередніх процесорах , але AFAIK безкоштовно на Skylake. Або, в гіршому випадку, додатковий взагалі.

  • Використовуйте пам'ять нижче espяк гігантську червону зону . Оскільки це повна програма для Linux, я знаю, що я не встановив жодних обробників сигналів, і що більше нічого не буде асинхронно обробляти стек пам'яті користувача. Це може бути не в інших ОС.

  • Скористайтеся механізмом стека, щоб зберегти пропускну здатність взагалі, використовуючи pop eax(1 взагалі + випадковий синхронізація стека взагалі) замість lodsd(2 уп на Haswell / Skylake, 3 на IvB і раніше відповідно до таблиць інструкцій Agner Fog )). IIRC, це знизило час роботи з приблизно 83 секунд до 73. Я, мабуть, міг би отримати ту ж швидкість від використання a movз індексованим режимом адресації, як, наприклад, mov eax, [edi+ebp]де ebpпроводиться зміщення між src і dst буферами. (Це зробило б код поза внутрішньою петлею більш складним, змусивши звести нанівець регістр зміщення як частину заміни src та dst для ітерацій Фібоначчі.) Докладніше див. У розділі "Продуктивність" нижче.

  • Почніть послідовність, надаючи першій ітерації перенесення (один байт stc), а не зберігати 1пам'ять у будь-якому місці. Багато інших матеріалів, що стосуються конкретних проблем, задокументовані в коментарях.

Перелік NASM (машинний код + джерело) , згенерований за допомогою nasm -felf32 fibonacci-1G.asm -l /dev/stdout | cut -b -28,$((28+12))- | sed 's/^/ /'. (Тоді я видалив декілька блоків коментованих матеріалів, тому нумерація рядків має прогалини.) Щоб викреслити провідні стовпці, щоб ви могли подати її в YASM або NASM, використовуйте cut -b 27- <fibonacci-1G.lst > fibonacci-1G.asm.

  1          machine      global _start
  2          code         _start:
  3 address

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

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

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

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

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

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

169                       to_string:

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

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

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

З цього, мабуть, є можливість пограти ще кілька байтів, але я вже витратив принаймні 12 годин на це протягом 2 днів. Я не хочу жертвувати швидкістю, навіть якщо це спосіб більш ніж швидкий і є можливість зробити його меншим способом, який витрачає швидкість . Частина моєї причини публікації повідомляє про те, наскільки швидко я можу зробити версию ASM грубої сили. Якщо хтось хоче дійсно отримати мінімальний розмір, але, можливо, на 10 разів повільніше (наприклад, 1 цифра на байт), сміливо скопіюйте це як вихідну точку.

Отриманий виконуваний файл (від yasm -felf32 -Worphan-labels -gdwarf2 fibonacci-1G.asm && ld -melf_i386 -o fibonacci-1G fibonacci-1G.o) становить 340В (позбавлений):

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

Продуктивність

Внутрішній adcцикл становить 10 Uops з плавленим доменом на Skylake (+1 стек-синхронізація взагалі кожні ~ 128 байт), тому він може видавати за один цикл на ~ 2,5 циклу на Skylake з оптимальною пропускною здатністю (ігноруючи стек-синхронізацію Uops) . Затримка критичного шляху становить 2 цикли, для adc-> cmp-> adcланцюга залежності наступної ітерації, що перебуває в циклі, тому вузьким місцем має бути гранична кількість випусків - 2,5 циклу за ітерацію.

adc eax, [edi + edx]є 2 Uops з конденсованим доменом для портів виконання: load + ALU. Він мікроплавкі в декодерах (1 загальнодоступний домен взагалі), але не ламінований на етапі випуску до 2 Uops з плавленим доменом, через індексований режим адресації, навіть на Haswell / Skylake . Я думав, що він залишиться мікроплавким, як add eax, [edi + edx]це робиться, але, можливо, зберігання режимів індексованої адреси мікро-злиття не працює для Uops, у яких вже є 3 входи (прапори, пам'ять та призначення). Коли я писав це, я думав, що це не матиме зниження продуктивності, але я помилявся. Цей спосіб поводження з усіченням щоразу сповільнює внутрішню петлю, будь edxто 0 або 4.

Швидше було б обробити зсув читання-запису для dst шляхом компенсації ediта за допомогою edxналаштування магазину. Так adc eax, [edi]/ ... / mov [edi+edx], eax/ lea edi, [edi+4]замість stosd. Haswell і пізніше можуть тримати індексований магазин мікроплавким. (Сендібрідж / IvB також розпалить його.)

У Intel Haswell і раніше, adcі cmovcце 2 уопи кожен, із затримкою 2с . ( adc eax, [edi+edx]досі не має ламінованого покриття на Haswell і видає 3 Uops з плавленим доменом) Бродвелл та пізніше дозволяють 3-вхідні Uops для більше, ніж просто FMA (Haswell), роблячи adcта cmovc(і ще пару речей) інструкції на одне ціле, як вони були на AMD вже давно. (Це одна з причин, що AMD вже давно успішно справляється з орієнтирами GMP з розширеною точністю.) У будь-якому випадку внутрішня петля Haswell повинна становити 12 уп (час від часу синхронізація +1 стека), з вузьким місцем - 3c на передній iter кращий випадок, ігноруючи стеки синхронізації стека.

Використання popбез балансування pushвсередині циклу означає, що цикл не може запускатися з LSD (детектор потокового циклу) , і його потрібно кожного разу перечитувати з загального кешу в IDQ. Якщо що-небудь, це добре на Skylake, оскільки цикл 9 або 10 взагалі не виходить оптимально за 4 уп кожного циклу . Це, мабуть, частина того, чому заміни lodsdна popдопомогли так сильно. (LSD не може зафіксувати uops, оскільки це не залишить місця для вставки синхронізації стека взагалі .) (BTW, оновлення мікрокоду відключає LSD повністю на Skylake та Skylake-X, щоб виправити помилку. Я виміряв значення вище, перш ніж отримати це оновлення.)

Я профілював його на Haswell і виявив, що він працює в 381,31 мільярда тактових циклів (незалежно від частоти процесора, оскільки він використовує лише кеш L1D, а не пам'ять). Пропускна здатність випуску на передньому кінці становила 3,72 Uops з плавним доменом на добу, порівняно з 3,70 для Skylake. (Але, звичайно, інструкції за цикл знизилися до 2,42 з 2,87, тому що adcі cmovна Haswell 2 уп.)

pushзаміна, stosdймовірно, не допоможе так сильно, тому adc [esp + edx]що викликає синхронізацію стека взагалі кожного разу. І коштував би байт, stdтому lodsdйде в іншому напрямку. ( mov [edi], eax/ lea edi, [edi+4]для заміни stosd- це виграш - від 32909 мотоциклів на 100 М ітерах до 31954 мотоциклів на 100 М ітерах. Схоже, що stosdдекодується як 3 уп, при цьому сховища адреси магазину / сховища даних не є мікроплавленими, тому push+ стек-синхронізація Uops, можливо, буде швидше, ніж stosd)

Фактична продуктивність ~ 322,47 мільярда циклів для 1G ітерацій 114 кінцівок працює до 2 824 циклів за ітерацію внутрішньої петлі , для швидкої версії 105B на Skylake. (Див. ocperf.pyВихід нижче). Це повільніше, ніж я прогнозував у статичному аналізі, але я ігнорував накладні витрати зовнішньої петлі та будь-які Uop-синхронізації стека.

Perf лічильники branchesі branch-missesпоказують, що внутрішній цикл неправильно прогнозується один раз на зовнішній цикл (на останній ітерації, коли він не зроблений). На це також припадає частина додаткового часу.


Я міг би зберегти розмір коду, зробивши, щоб внутрішній цикл мав тривалість затримки для критичного шляху, використовуючи mov esi,eax/ sub eax,ebp/ cmovc eax, esi/cmc (2 + 2 + 3 + 1 = 8B) замість lea esi, [eax - 1000000000]/ cmp ebp,eax/ cmovc(6 + 2 + 3 = 11B ). cmov/ stosdВиключений критичний шлях. (Приріст-edi взагалі stosdможе працювати окремо від магазину, тому кожна ітерація відщеплює короткий ланцюг залежностей.) Він використовував для збереження іншого 1B, змінюючи інструкцію ebp init з lea ebp, [ecx-1]на mov ebp,eax, але я виявив, що помилкаebpне змінив результат. Це дозволить кінцівці точно == 1000000000 замість того, щоб загортати та виробляти перенесення, але ця помилка поширюється повільніше, ніж у нас зростає Fib (), тому це не відбувається, щоб змінити провідні 1k цифри кінцевого результату. Крім того, я вважаю, що помилка може виправити себе, коли ми просто додаємо, оскільки є місце в кінцівці, щоб утримати її без переповнення. Навіть 1G + 1G не переповнює 32-бітове ціле число, тож воно в підсумку проникне вгору або буде усічене.

Версія 3с затримки - це 1 додатковий загальний вигляд, тому передній кінець може видавати її за один раз на 2,75 циклу на Skylake, лише трохи швидше, ніж задній. (У Haswell це буде 13 уп, оскільки він досі використовує adcта cmov, і вузьке місце на передньому кінці - 3,25c на ітер).

На практиці він працює на 1,18 повільніше на Skylake (3,34 цикла на кінцівку), а не 3 / 2,5 = 1,2, який я передбачив, щоб замінити переднє місце вузьким місцем затримкою, ніж просто дивитися на внутрішню петлю без синхронізації упс. Оскільки Uops стеки-синхронізації завдають шкоди тільки швидкій версії (вузькі місця на передній частині замість затримки), для її пояснення не потрібно багато. наприклад 3 / 2,54 = 1,18.

Іншим фактором є те, що версія затримки 3c може виявити помилковий прогноз щодо виходу з внутрішнього циклу, поки критичний шлях все ще виконується (оскільки передній кінець може випереджати його від заднього, дозволяючи виконанню поза замовленням запуску циклу- зустрічний uops), тому ефективний неправильний прогноз є меншим. Втрата цих циклів на передньому кінці дозволяє наздогнати його.

Якби не це, ми, можливо, могли б прискорити 3c- cmcверсію, використовуючи гілку у зовнішньому циклі замість безвідвідного поводження з компенсацією transfer_out -> edx та esp. Прогнозування гілки + спекулятивне виконання для залежності управління замість залежності даних могло б дати можливість наступній ітерації почати adcцикл, поки Uops з попереднього внутрішнього циклу все ще знаходився в польоті. У безгалузевій версії адреси навантаження у внутрішньому циклі мають залежність даних від CF від останньої adcз останньої кінцівки.

2с затримка внутрішньої петлі у вузькій версії на передній частині, тому задній кінець майже не відстає. Якщо код зовнішньої петлі був з високою затримкою, передній кінець може випереджати видачу Uops від наступної ітерації внутрішнього циклу. (Але в цьому випадку у зовнішньому циклі є багато ILP та відсутність високозатримки, тож у зворотному кінці не так вже й багато чого робити, коли він починає пережовувати Uops у планувальнику поза замовлення, як їх вхід стає готовим).

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

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

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

      73.436831004 seconds time elapsed                                          ( +-  0.05% )

( +- x %)- це стандартне відхилення протягом 4 прогонів для цього рахунку. Цікаво, що він виконує таку кругленьку кількість інструкцій. Ці 924 мільярди - не випадковість. Я здогадуюсь, що зовнішній цикл виконує загалом 924 інструкції.

uops_issuedє числом злитих доменів (актуально для пропускної спроможності випуску), в той час uops_executedяк кількість незрощених доменів (кількість Uops, що надсилаються на порти виконання). Micro-Fusion пакує 2 Uops з конденсованим доменом в один злитий домен взагалі, але mov-усунення означає, що деяким Uops з плавленим доменом не потрібні порти виконання. Дивіться пов’язане запитання, щоб отримати докладнішу інформацію про підрахунок Uops та злитого до неплавленого домену. (Також дивіться таблиці інструкцій Agner Fog та посібник uarch та інші корисні посилання у вікі тегів SO x86 ).

З іншого запуску вимірювання різних речей: пропуски кеш-пам’яті L1D абсолютно незначні, як очікується, для читання / запису тих самих двох буферів 456B. Гілка внутрішнього циклу неправильно прогнозує один раз на зовнішню петлю (коли не потрібно брати цикл). (Загальний час вище, тому що комп'ютер не був повністю без роботи. Напевно, інший логічний ядро ​​деякий час був активним, і більше часу проводилося в перервах (оскільки частота вимірюваного простору користувача була нижче 4,400 ГГц). Або декілька ядер були активнішими більше часу, знижуючи макс. Турбо. Я не відслідковував, cpu_clk_unhalted.one_thread_activeчи є проблема конкуренції HT.)

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

Мій код цілком може працювати на меншій кількості циклів на Ryzen, що може видавати 5 уоп за цикл (або 6, коли деякі з них мають 2-загальні інструкції, як AVX 256b на Ryzen). Я не впевнений, що зробить його фронт-енд stosd, це 3 уп на Ryzen (те саме, що і Intel). Я думаю, що інші вказівки у внутрішній петлі - це така ж затримка, що і Skylake та всі однократні. (У тому числі adc eax, [edi+edx], що є перевагою перед Skylake).


Це, ймовірно, може бути значно меншим, але, можливо, на 9 разів повільніше, якби я зберігав числа як 1 десяткова цифра на байт . Генерування з допомогою cmpта коригування з цим cmovбуде працювати однаково, але зробіть 1/9 роботу. 2 десяткові цифри на байт (база-100, а не 4-бітний BCD з повільнимDAA ) також працюватимуть, і div r8/ add ax, 0x3030перетворює 0-99 байт на дві цифри ASCII в порядку друку. Але 1 цифра на байт взагалі не потрібна div, просто циклічне додавання та додавання 0x30. Якщо я зберігаю байти в порядку друку, це зробить другий цикл справді простим.


Використання 18 або 19 десяткових цифр на 64-бітне ціле число (в 64-бітному режимі) змусить його працювати приблизно вдвічі швидше, але коштує значного розміру коду для всіх префіксів REX і для 64-бітних констант. 32-бітні кінцівки в 64-бітному режимі не дозволяють використовувати pop eaxзамість цього lodsd. Я все-таки міг уникати префіксів REX, використовуючи espяк регістр подряпин, що не вказують (заміняючи використання esiта esp), замість того, щоб використовувати r8dяк 8-й регістр.

Якщо робити версію з функцією дзвінка, перетворення на 64-бітну та використання r8dможе бути дешевше, ніж збереження / відновлення rsp. 64-бітний також не може використовувати однобайтове dec r32кодування (оскільки це префікс REX). Але в основному я закінчив використовувати dec bl2 байти. (Оскільки у мене є константа у верхніх байтах ebx, і я використовую її лише за межами внутрішніх циклів, що працює, оскільки низький байт постійної 0x00.)


Високопродуктивна версія

Для досягнення максимальної продуктивності (не для коду-гольфу), ви хочете розкрутити внутрішній цикл, щоб він працював щонайменше за 22 ітерацій, що є досить коротким, прийнятим / неприйнятим шаблоном для того, щоб гілки-прогнозисти спрацювали добре. У моїх експериментах mov cl, 22перед .inner: dec cl/jnz .innerциклом є дуже мало помилок (наприклад, 0,05%, що набагато менше одного за повний пробіг внутрішньої петлі), але mov cl,23неправильно прогнозує від 0,35 до 0,6 разів за внутрішній цикл. 46особливо погано, непередбачувано ~ 1,28 рази за внутрішню петлю (128М разів для 100М ітерацій зовнішньої петлі). 114неправильно передбачили рівно один раз на внутрішню петлю, те саме, що я виявив у складі циклу Фібоначчі.

Мені стало цікаво і спробував це, розгорнувши внутрішню петлю на 6 з а %rep 6(бо це розділяє 114 рівномірно). Це здебільшого усувало недоліки в галузі. Я зробив edxнегатив і використовував це як компенсацію для movмагазинів, щоб adc eax,[edi]міг залишатися мікроплавким. (І так я міг уникнути stosd). Я витягнув leaоновлення ediз %repблоку, тож він робить лише одне оновлення покажчика на 6 магазинів.

Я також позбувся всіх матеріалів з частковим реєстром у зовнішній петлі, хоча не думаю, що це було суттєвим. Можливо, це трохи допомогло, щоб CF на кінці зовнішньої петлі не залежав від кінцевої АЦП, тому деякі внутрішні петлі можуть бути розпочаті. Код зовнішньої петлі, можливо, можна було б оптимізувати трохи більше, оскільки це neg edxбуло останнє, що я зробив, замінивши xchgлише дві movінструкції (оскільки у мене вже було 1), і перевпорядкувавши ланцюги dep разом із скиданням 8-розрядних реєструвати речі.

Це джерело NASM саме з циклу Фібоначчі. Це заміна, що випадає для цього розділу оригінальної версії.

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

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

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

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

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

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

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

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

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

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

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

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

Продуктивність:

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

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

      62.279833889 seconds time elapsed                                          ( +-  0.07% )

Це для тієї ж Fib (1G), що дає той же вихід за 62,3 секунди замість 73 секунд. (Цикли 273.146G проти 322.467G. Оскільки все потрапляє в кеш L1, базові цикли годин - це все, що нам потрібно подивитися).

Зверніть увагу на значно менший загальний uops_issuedпідрахунок, що значно нижче uops_executed. Це означає, що багато з них були мікроплавкими: 1 взагалі у злитому домені (випуск / ROB), але 2 уоп у неплавленому домені (планувальник / одиниці виконання). І мало хто був усунутий на етапі випуску / перейменування (наприклад, movкопіювання реєстру або xor-zeroing, яке потрібно видавати, але не потребує блоку виконання). Усунуті uops не врівноважують кількість рахунків в інший спосіб.

branch-missesзнижується до ~ 400k, з 1G, тому розгортання працювало. resource_stalls.anyзараз важливо, а це означає, що передня частина вже не є вузьким місцем: натомість задній кінець відстає і обмежує передню частину. idq_uops_not_delivered.coreпідраховує лише цикли, коли фронтальний наконечник не видав Uops, але задній не був застопорений. Це приємно і низько, що вказує на кілька передніх вузьких місць.


Веселий факт: версія python витрачає більше половини свого часу, діючи на 10, а не додаючи. (Заміна a/=10зі a>>=64швидкістю його більш ніж в 2 рази, але змінює результат , тому що двійковий урізання = десяткове усічення.)

Моя версія ASM, звичайно, оптимізована спеціально для цього розміру проблеми, ітерація циклу - важко кодована. Навіть зміщення довільно точного числа скопіює його, але моя версія може просто прочитати зі зміщення для наступних двох ітерацій, щоб пропустити навіть це.

Я профілював версію python (64-бітний python2.7 в 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

Числа в (паронах) - це кількість часу, на який проводили вибірку лічильника парфумів. Якщо дивитися на більше лічильників, ніж опори HW, перф обертається між різними лічильниками та екстраполятами. Це абсолютно добре для тривалого виконання одного і того ж завдання.

Якби я побіг perfпісля встановлення sysctl kernel.perf_event_paranoid = 0(або запустив perfяк root), він міряв би 4.400GHz. cycles:uне враховує час, витрачений на переривання (або системні виклики), лише цикли простору користувача. Мій робочий стіл майже повністю працював, але це типово.


20

Haskell, 83 61 байт

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

Виходи ( F 1000000000 , F 1000000001 ). На моєму ноутбуці він правильно роздруковує ліву батьківську і першу 1000 цифр протягом 133 секунд, використовуючи 1,35 ГБ пам'яті.

Як це працює

Повторність Фібоначчі може бути вирішена за допомогою матричної експоненції:

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

з якої ми отримуємо ці тотожності:

[ 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 .

У pфункції обчислює ( Р я + J , Р я + J + 1 ) з урахуванням ( Р я , Р я + 1 ) і ( F J , F J + 1 ). Пишучи f nдля ( F i , F i + 1 ), маємо p (f i) (f j)= f (i + j).

Тоді,

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

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

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

і підключаємо f 1= (1,1).


12

Математика, 15 34 байт

Fibonacci сама займає ~ 6s на моєму комп’ютері. І 95 (+/- 5) s для інтерфейсу для його відображення.

Fibonacci@1*^9&

введіть тут опис зображення

Перші 1000 цифр (34 байти): ⌊Fibonacci@1*^9/1*^208986640⌋&

тест 1

Більше, але швидше ToString@Fibonacci@1*^9~StringTake~1000&:

тестовий скріншот


1
6 секунд ?! Який комп’ютер ти працюєш? Минуло моє 140 секунд! (також, чи дійсно вам потрібно 10 разів більше, щоб перетворити його на рядок і отримати перші 1000 символів, ніж просто обчислити його?)
numbermaniac

1
@numbermaniac Вибачте, я мушу уточнити, що для відображення номера на фронтеді потрібно набагато більше часу.
Кейу Ган

1
@numbermaniac: Ці часи мене не дуже дивують. Внутрішньо результат Фібоначчі, ймовірно, знаходиться в base2, а обчислення IIRC N-го числа Фібоначчі можна робити в операціях O (log (n)); Математика, безумовно, не просто жорстоко пробиває свій шлях через масивні доповнення BigInteger. IDK мовою, що добре; можливо, це використовує частково ледачу оцінку, щоб уникнути фактичного створення BigInteger 71,5 МБ.
Пітер Кордес

2
@numbermaniac: Що ще важливіше, внутрішнє представлення знаходиться в base2, а для перетворення на рядок base10 потрібно повторне ділення на 10. Ділення цілого числа набагато повільніше, ніж ціле множення для 64-бітових цілих чисел, і це так само погано для дворегістрової розширеної точності (якщо не гірше, тому що множитися в конвеєрному режимі краще, ніж ділити навіть у дуже недавніх процесорах x86 з досить хорошим обладнанням для поділу). Я припускаю, що це так само погано для довільної точності, навіть для малого постійного дільника, як 10.
Пітер Кордес

1
Я дивився відповідь машинного коду x86 на це запитання і весь час розглядав питання про те, щоб моє число було десятковим. Це здебільшого полягало в тому, щоб скоротити реалізацію, взагалі не потребуючи розгорнутого циклу поділу. (Я думав, можливо, з 2 цифрами на байт (0..99) або 0..1e9-1 на 32-бітний шматок, тому кожен фрагмент перетворюється на постійну кількість десяткових цифр, і я можу просто використовувати div). Я зупинився, оскільки люди, мабуть, зробили б перегляд цього питання до того моменту, коли у мене була добре гольф-функція, яка зробила всю цю роботу. Але, мабуть, груба сила може спрацювати, як показують деякі відповіді.
Пітер Кордес

11

Python 2, 70 байт

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

На моєму ноутбуці це пробігло 18 хвилин і 31 секунду, при цьому вийшло вірно 1000 цифр, за якими 74100118580(правильні наступні цифри 74248787892).


Гарний поєднання грубої сили та економії роботи.
Пітер Кордес

Оскільки це свідчить про те, що працює досить простий підхід з грубою силою, я думав реалізувати це в машинному коді x86. Можливо, я міг би змусити його працювати в 100 до 200 байт, роблячи всі речі з підвищеною точністю вручну, звичайно, але це потребує значного часу на розробку, особливо для того, щоб гольф + оптимізував це. У моєму плані було 32-розрядні фрагменти base10 ** 9, тому легко скоротити до 1006 цифр і легко перетворити на десятковий рядок без довільної точності поділу. Просто divцикл, щоб зробити 9 десяткових цифр за шматок. Проводьте під час додавання cmp / cmov та 2xADD замість ADC.
Пітер Кордес

Думаючи про це достатньо, щоб набрати цей попередній коментар, мене зачепило. Я в кінцевому підсумку реалізував його в 106 байтах 32-розрядного машинного коду x86, використовуючи цю ідею, працює на 1min13s на моєму комп’ютері проти 12min35s на моєму робочому столі для цієї версії python (що витрачає більшу частину свого часу на ділення на 10, що не швидко для розширеної точності номери base2!)
Пітер Кордес

10

Haskell , 78 байт

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

Спробуйте в Інтернеті!

Потрапив на TIO 48 секунд. Така сама рекурсивна формула, як і моя відповідь Python , але без обрізання.

Константа 2143923439є 10**9-1, зворотна у двійковій формі, та з додатковою 1 в кінці. Ітерація через її двійкові цифри в зворотному порядку імітує повторення через двійкові цифри 10**9-1. Цей жорсткий код здається коротшим, ніж обчислити його.


9

Haskell , 202 184 174 173 170 168 164 162 байт

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

Спробуйте в Інтернеті!

Пояснення

Це використовує досить швидкий спосіб обчислення чисел числа. Функція lприймає два числа полів і обчислює номери напруженості 10 пізніше, тоді як fприймає n- й і n + 1- й цифри напруги і обчислює 2n + 20- е і 2n + 21- е поле числа. Я їх ланцюжком доволі випадково отримую 1 мільярд і збираю перші 1000 цифр.


Ви можете зберегти кілька байтів, застосувавши оператора, який складає функцію з себе n разів.
user1502040

@ user1502040, тобто церковні цифри.
Флоріан F

8

Haskell, 81 байт

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

Пояснення

f nрекурсивно обчислює nчисло номер третього рівня, використовуючи повторення з відповіді xnor із усуненням загальної субекспресії. На відміну від інших розміщених рішень, які використовують множення на O (log (n)), у нас є O (log (n)) - глибина рекурсії з коефіцієнтом розгалуження 2 для складності множин на O (n).

Однак все не втрачено! Оскільки майже всі дзвінки будуть знаходитись у нижній частині дерева рекурсії, ми можемо використовувати швидку натурну арифметику, де це можливо, і уникнути безлічі маніпуляцій з величезними бінтумами. Він виписує відповідь через пару хвилин на мій ящик.


8

T-SQL, 422 414 453 байт (перевірено, зараз змагаються!)

EDIT 2 : Змінено , набрав кілька байт, але збільшив швидкість, щоб досягти 1 мільярда! Виконується за 45 годин 29 хвилин , підтверджує відповідність заданому рядку та відображає додаткові 8 символів (що може бути, а може і не бути правильним через помилки округлення).INT BIGINT DECIMAL(37,0)

T-SQL не має вбудованої підтримки "величезної кількості", тому довелося згорнути власний текстовий величезний додаток чисел за допомогою рядків 1008 символів:

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

Ось відформатована версія з коментарями:

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

В основному я вручну маніпулюю 100-символьними рядками, заповненими нулями, що представляють мої дві змінні Фібоначчі, @aі @.

Я додаю їх 8 18 36 цифр за один раз, знімаючи останні 36 цифр, перетворюючи на керований числовий тип ( DECIMAL(37,0)), додаючи їх, потім перекочуючи його назад в інший довгий рядок @c. Потім я "обертаюсь" @aі @, переміщаючи останні 36 цифр вперед, і повторюю процес. 28 обертів * 36 цифр охоплює всі 1008. Я повинен "перенести ту" вручну.

Як тільки наша кількість починає перевищувати мою довжину рядка, я "зміщуся вліво" і ми починаємо втрачати певну точність, але помилка виявляється в межах моїх додаткових символів.

Я спробував використовувати таблицю SQL, повну INT і BIGINT, з подібною логікою, і це було значно повільніше. Дивно.


7
Вражаючі зловживання ресурсами компанії!
davidbak

6

PARI / GP, 45 байт

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

Якось \p1000недостатньо. Це не працює з 32-бітовими системами. Остаточний поділ полягає у униканні десяткової крапки в наукових позначеннях.


4

Парі / GP , 15 + 5 = 20 байт

fibonacci(10^9)

Запустіть за допомогою параметра командного рядка, -s1gщоб виділити 1 Гбайт пам'яті.


1

Рубін, 63 байти

чоловіче, я погано граю в рубіні для гольфу; але клас BigInt творить чудеса для подібних матеріалів. Ми використовуємо той же алгоритм, що і Андерс Касеорг.

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

Це насправді отримує тисячу цифр?
dfeuer
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.