Ada satu miliar iterasi tantangan Fibonacci di situs web ini, jadi mari kita tambahkan semuanya dengan tantangan Fibonacci dari satu miliar iterasi!
Tantangan Anda adalah menghasilkan 1000 digit desimal pertama dari angka Fibonacci 1.000.000.000 dengan program sesingkat mungkin. Ini kemudian secara opsional dapat diikuti oleh output tambahan apa pun yang Anda pilih, termasuk tetapi tidak terbatas pada digit lainnya.
Saya menggunakan konvensi yang fib 0 = 0
, fib 1 = 1
.
Program Anda harus cukup cepat untuk menjalankannya dan memverifikasi kebenarannya. Untuk tujuan ini, berikut adalah 1000 digit pertama:
7952317874554683467829385196197148189255542185234398913453039937343246686182519370050999626136556779332482035723222451226291714456275648259499530612111301255499879639516053459789018700567439946844843034599802419924043753401950114830107234265037841426980398387360784284231996457340782784200767760907777703183185744656536253511502851715963351023990699232595471322670365506482435966586886048627159716916351448788527427435508113909167963907380398242848033980110276370544264285032744364781198451825462130529529633339813483105771370128111851128247136311414208318983802526907917787094802217750859685116363883374847428036737147882079956688807509158372249451437519320162582002000530798309887261257028201907509370554232931107084976854715833585623910450679449120011564762925649144509531904684984417002512086504020779012501356177874199605085558317190905395134468919443313026824813363234190494375599262553025466528838122639433600483849535070647711986769279568548796855207684897741771784375859496425384355879105799
code-golf
kolmogorov-complexity
fibonacci
restricted-time
pengguna1502040
sumber
sumber
Your program must be fast enough for you to run it and verify its correctness.
bagaimana dengan memori?a+=b;b+=a;
loop (mungkin dengan Java BigInteger) adalah pilihan yang jelas, setidaknya jika Anda bahkan berpikir tentang kinerja. Implementasi rekursif selalu tampak sangat tidak efisien bagi saya.write()
panggilan sistem). Saya suka persyaratan kinerja, yang membuatnya jauh lebih menyenangkan bagi saya.Jawaban:
Python 2 + sympy, 72 byte
Cobalah online!
-10 byte dengan menghapus istilah-0 praktis berkat Jeff Dege
-1 byte (1000 -> 1e3 berkat Zacharý)
-2 byte dengan menghapus variabel yang tidak perlu berkat Erik the Outgolfer
-2 byte dengan pindah ke Python 2 berkat Zacharý
-3 bytes dengan mengucapkan
-11
terima kasih kepada ThePirateBay -3 bytes dengan menukarstr
backtick berkat notjagansekarang mengalahkan solusi haskell OP yang belum diposkan!
sumber
from sympy import*;sqrt
tidak ada byte yang disimpanimport sympy;sympy.sqrt
:)sympy
adalah paket matematika simbolis untuk Python sehingga tidak ada masalah dengan kesalahan pembulatan, setidaknya sampai angka yang sangat besar (angka ini tidak cukup besar lol). Kemudian saya hanya menghitungnya untuk memberi saya angka 1e3 pertama karena jika tidak, jika Anda menghapus.evalf(1e3)
bagian itu, itu memberi saya representasi notasi ilmiah yang sangat pendek.Python 2 , 106 byte
Cobalah online!
Tidak ada perpustakaan, hanya aritmatika integer. Berjalan hampir seketika.
Inti adalah identitas divide-and-conquer:
Ini memungkinkan kami memperbarui
(a,b) = (f(n),f(n+1))
menjadi dua kali lipatn -> 2*n
. Karena kami ingin mendapatkann=10**9
, ini hanya membutuhkanlog_2(10**9)=30
iterasi. Kami membangunn
hingga10**9
dengan berulang kali melakukann->2*n+c
untuk setiap digitc
ekspansi biner. Ketikac==1
, nilai berlipat digeser ke atas2*n -> 2*n+1
dengan pergeseran Fibonacci satu langkah(a,b)=(b+a,b)
Agar nilai-nilai
a,b
dapat dikelola, kami hanya menyimpan1006
digit pertama mereka dengan membagi lantai10
sampai mereka berada di bawah2**3340 ~ 1e1006
.sumber
a,b,c=a*a+b*b,a*a-c*c,b*b+c*c
,.x86 kode mesin 32-bit (dengan panggilan sistem Linux):
106105 bytechangelog: menyimpan byte dalam versi cepat karena konstanta off-by-one tidak mengubah hasil untuk Fib (1G).
Atau 102 byte untuk versi 18% lebih lambat (pada Skylake) (menggunakan
mov
/sub
/cmc
bukannyalea
/cmp
di loop dalam, untuk menghasilkan carry-out dan pembungkus di10**9
bukan2**32
). Atau 101 byte untuk versi yang lebih lambat ~ 5.3x dengan cabang dalam carry-handling di loop paling dalam. (Saya mengukur tingkat mispredict cabang 25,4%!)Atau 104/101 byte jika nol di depan diizinkan. (Dibutuhkan 1 byte tambahan untuk hard-code melewatkan 1 digit dari output, yang merupakan apa yang diperlukan untuk Fib (10 ** 9)).
Sayangnya, mode NASM TIO tampaknya diabaikan
-felf32
dalam flag-flag compiler. Berikut adalah tautan dengan kode sumber lengkap saya, dengan semua kekacauan gagasan eksperimental dalam komentar.Ini adalah program yang lengkap . Mencetak 1000 digit pertama Fib (10 ** 9) diikuti oleh beberapa digit tambahan (beberapa yang terakhir salah) diikuti oleh beberapa bytes sampah (tidak termasuk baris baru). Sebagian besar sampah adalah non-ASCII, jadi Anda mungkin ingin menyalurkannya
cat -v
. Tapi itu tidak merusak emulator terminal saya (KDEkonsole
). "Sampah byte" menyimpan Fib (999999999). Saya sudah punya-1024
di register, jadi lebih murah untuk mencetak 1024 byte daripada ukuran yang tepat.Saya menghitung hanya kode mesin (ukuran segmen teks dari executable statis saya), bukan fluff yang membuatnya menjadi executable ELF. ( Executable ELF sangat kecil adalah mungkin , tapi saya tidak mau repot dengan itu). Ternyata lebih pendek menggunakan memori stack daripada BSS, jadi saya bisa membenarkan tidak menghitung apa pun dalam biner karena saya tidak bergantung pada metadata. (Memproduksi biner statis yang dilucuti dengan cara biasa membuat ELF 340 byte dapat dieksekusi.)
Anda dapat membuat fungsi dari kode ini yang dapat Anda panggil dari C. Dibutuhkan beberapa byte untuk menyimpan / mengembalikan stack pointer (mungkin dalam register MMX) dan beberapa overhead lainnya, tetapi juga menyimpan byte dengan kembali dengan string. dalam memori, alih-alih melakukan
write(1,buf,len)
panggilan sistem. Saya pikir golf dalam kode mesin harus memberi saya sedikit kelonggaran di sini, karena tidak ada orang lain yang bahkan memposting jawaban dalam bahasa apa pun tanpa ketelitian asli yang diperluas, tetapi saya pikir versi fungsi ini masih harus di bawah 120 byte tanpa golf ulang keseluruhan benda.Algoritma:
brute force
a+=b; swap(a,b)
, memotong seperlunya untuk menjaga hanya angka desimal terdepan> = 1017. Ini berjalan dalam 1 menit 13 detik di komputer saya (atau 322,47 miliar siklus clock + - 0,05%) (dan bisa beberapa% lebih cepat dengan beberapa byte tambahan ukuran kode, atau turun ke 62an dengan ukuran kode yang jauh lebih besar dari loop membuka gulungan. Tidak matematika pintar, hanya melakukan pekerjaan yang sama dengan overhead yang lebih sedikit). Ini didasarkan pada implementasi Python @ AndersKaseorg , yang berjalan dalam 12 menit 35 pada komputer saya (4.4GHz Skylake i7-6700k). Tidak ada versi yang memiliki cache L1D meleset, jadi DDR4-2666 saya tidak masalah.Tidak seperti Python, saya menyimpan angka presisi yang diperluas dalam format yang membuat pemotongan angka desimal gratis . Saya menyimpan grup dengan 9 angka desimal per bilangan bulat 32-bit, jadi offset pointer membuang angka 9 yang rendah. Ini secara efektif basis 1-miliar, yang merupakan kekuatan 10. (Ini kebetulan murni bahwa tantangan ini membutuhkan angka Fibonacci 1 miliar, tapi itu menyelamatkan saya beberapa byte vs dua konstanta terpisah.)
Mengikuti terminologi GMP , setiap potongan 32-bit dari nomor dengan ketepatan yang diperluas disebut "ekstremitas". Melakukan sambil menambahkan harus dihasilkan secara manual dengan membandingkan terhadap 1e9, tetapi kemudian digunakan secara normal sebagai input ke
ADC
instruksi biasa untuk anggota tubuh berikutnya. (Saya juga harus membungkus secara manual ke[0..999999999]
rentang, daripada pada 2 ^ 32 ~ = 4.295e9. Saya melakukan ini tanpa cabang denganlea
+cmov
, menggunakan hasil carry-out dari perbandingan.)Ketika tungkai terakhir menghasilkan non-zero carry-out, dua iterasi berikutnya dari loop luar membaca dari 1 tungkai lebih tinggi dari biasanya, tetapi masih menulis ke tempat yang sama. Ini seperti melakukan
memcpy(a, a+4, 114*4)
pergeseran ke kanan sebanyak 1 ekstremitas, tetapi dilakukan sebagai bagian dari dua loop tambahan berikutnya. Ini terjadi setiap ~ 18 iterasi.Hacks untuk penghematan ukuran dan kinerja:
Hal-hal yang biasa saya sukai
lea ebx, [eax-4 + 1]
alih - alihmov ebx, 1
, ketika saya tahu itueax=4
. Dan menggunakanloop
di tempat-tempat di manaLOOP
kelambatan hanya memiliki dampak kecil.Potong dengan 1 anggota badan secara gratis dengan mengimbangi pointer yang kita baca, sambil tetap menulis ke awal buffer di
adc
loop dalam. Kami membaca dari[edi+edx]
, dan menulis ke[edi]
. Jadi kita bisa mendapatkanedx=0
atau4
mendapatkan offset baca-tulis untuk tujuan. Kita perlu melakukan ini untuk 2 iterasi berturut-turut, pertama mengimbangi keduanya, kemudian hanya mengimbangi dulu. Kami mendeteksi kasing ke-2 dengan melihatesp&4
sebelum menyetel ulang pointer ke bagian depan buffer (menggunakan&= -1024
, karena buffer disejajarkan). Lihat komentar dalam kode.Lingkungan proses-startup Linux (untuk eksekusi statis) nol paling banyak register, dan tumpukan-memori di bawah
esp
/rsp
adalah nol. Program saya memanfaatkan ini. Dalam versi fungsi-terpanggil dari ini (di mana tumpukan yang tidak terisi dapat menjadi kotor), saya bisa menggunakan BSS untuk memusatkan memori (dengan biaya mungkin 4 byte lebih untuk mengatur pointer). Zeroingedx
akan membutuhkan 2 byte. System V ABI x86-64 tidak menjamin salah satu dari ini, tetapi implementasi Linux tidak nol (untuk menghindari kebocoran informasi dari kernel). Dalam proses yang terhubung secara dinamis,/lib/ld.so
jalankan sebelumnya_start
, dan biarkan register tidak nol (dan mungkin sampah di memori di bawah stack pointer).Aku tetap
-1024
diebx
untuk digunakan di luar loop. Gunakanbl
sebagai penghitung untuk loop dalam, berakhir dengan nol (yang merupakan byte rendah-1024
, sehingga mengembalikan konstanta untuk digunakan di luar loop). Intel Haswell dan kemudian tidak memiliki hukuman penggabungan sebagian daftar untuk register low8 (dan bahkan tidak mengubah nama mereka secara terpisah) , jadi ada ketergantungan pada register lengkap, seperti pada AMD (tidak masalah di sini). Namun hal ini akan sangat mengerikan di Nehalem dan sebelumnya, yang memiliki warung-warung pendaftaran sebagian saat penggabungan. Ada tempat-tempat lain di mana saya menulis reg sebagian dan kemudian membaca reg penuh tanpa-xor
nol atau amovzx
, biasanya karena saya tahu bahwa beberapa kode sebelumnya memusatkan byte atas, dan sekali lagi itu baik-baik saja pada AMD dan Intel SnB-family, tetapi lambat pada Intel pra-Sandybridge.Saya menggunakan
1024
jumlah byte untuk menulis ke stdout (sub edx, ebx
), jadi program saya mencetak beberapa byte sampah setelah digit Fibonacci, karenamov edx, 1000
biaya lebih banyak byte.(tidak digunakan)
adc ebx,ebx
dengan EBX = 0 untuk mendapatkan EBX = CF, menyimpan 1 byte vssetc bl
.dec
/jnz
di dalam satuadc
loop mempertahankan CF tanpa menyebabkan parsial-bendera terhenti ketikaadc
membaca bendera di Intel Sandybridge dan kemudian. Ini buruk pada CPU sebelumnya , tetapi AFAIK gratis di Skylake. Atau paling buruk, uop ekstra.Gunakan memori di bawah ini
esp
sebagai zona merah raksasa . Karena ini adalah program Linux yang lengkap, saya tahu saya tidak menginstal penangan sinyal apa pun, dan tidak ada yang lain yang secara tidak sinkron akan menumpuk memori ruang pengguna. Ini mungkin tidak terjadi pada OS lain.Manfaatkan mesin stack untuk menghemat bandwidth masalah uop dengan menggunakan
pop eax
(1 uop + stack-sync uop sesekali) alih-alihlodsd
(2 uops di Haswell / Skylake, 3 di IvB dan sebelumnya menurut tabel instruksi Agner Fog )). IIRC, ini menurunkan run-time dari sekitar 83 detik menjadi 73. Saya mungkin bisa mendapatkan kecepatan yang sama dari menggunakanmov
dengan mode pengalamatan terindeks, seperti dimov eax, [edi+ebp]
manaebp
memegang offset antara buffer src dan dst. (Ini akan membuat kode di luar loop dalam lebih kompleks, harus meniadakan register offset sebagai bagian dari swapping src dan dst untuk iterasi Fibonacci.) Lihat bagian "kinerja" di bawah ini untuk informasi lebih lanjut.mulai urutan dengan memberikan iterasi pertama carry-in (satu byte
stc
), alih-alih menyimpan1
dalam memori di mana saja. Banyak hal khusus masalah lain yang didokumentasikan dalam komentar.Daftar NASM (kode mesin + sumber) , dibuat dengan
nasm -felf32 fibonacci-1G.asm -l /dev/stdout | cut -b -28,$((28+12))- | sed 's/^/ /'
. (Kemudian saya menghapus beberapa blok dari hal-hal yang dikomentari, sehingga penomoran baris memiliki celah.) Untuk menghapus kolom terkemuka sehingga Anda dapat memasukkannya ke YASM atau NASM, gunakancut -b 27- <fibonacci-1G.lst > fibonacci-1G.asm
.Mungkin ada ruang untuk bermain golf beberapa byte lagi dari ini, tapi saya sudah menghabiskan setidaknya 12 jam untuk ini selama 2 hari. Saya tidak ingin mengorbankan kecepatan, meskipun cara itu lebih dari cukup cepat dan ada ruang untuk membuatnya lebih kecil dengan biaya kecepatan . Bagian dari alasan saya untuk memposting adalah menunjukkan seberapa cepat saya dapat membuat versi asm brute-force. Jika ada yang ingin benar-benar menggunakan ukuran minimum tetapi mungkin 10x lebih lambat (mis. 1 digit per byte), jangan ragu untuk menyalin ini sebagai titik awal.
Eksekusi yang dihasilkan (dari
yasm -felf32 -Worphan-labels -gdwarf2 fibonacci-1G.asm && ld -melf_i386 -o fibonacci-1G fibonacci-1G.o
) adalah 340B (dilucuti):Performa
adc
Loop dalam adalah 10 uop domain-menyatu pada Skylake (+1 stack-sync uop setiap ~ 128 byte), sehingga ia dapat mengeluarkan satu siklus per 2.5 siklus pada Skylake dengan throughput front-end yang optimal (mengabaikan uop stack-sync) . Latensi jalur kritis adalah 2 siklus, untukadc
->cmp
->adc
rantai ketergantungan loop berikutnya yang diangkut iterasi , sehingga bottleneck harus menjadi batas masalah front-end ~ 2,5 siklus per iterasi.adc eax, [edi + edx]
adalah 2 domain yang tidak digunakan untuk port eksekusi: load + ALU. Ini micro-sekering dalam decoder (1 domain-leburan-uop), tetapi un-laminasi dalam tahap masalah ke 2-domain-leburan-uops, karena mode pengalamatan diindeks, bahkan pada Haswell / Skylake . Saya pikir itu akan tetap micro-fused, sepertiadd eax, [edi + edx]
halnya, tetapi mungkin menjaga mode pengalamatan terindeks micro-fused tidak bekerja untuk uops yang sudah memiliki 3 input (flag, memori, dan tujuan). Ketika saya menulisnya, saya berpikir itu tidak akan memiliki penurunan kinerja, tetapi saya salah. Cara penanganan pemotongan ini memperlambat loop dalam setiap kali, apakahedx
0 atau 4.Akan lebih cepat untuk menangani offset baca-tulis untuk dst dengan mengimbangi
edi
dan menggunakanedx
untuk menyesuaikan toko. Jadiadc eax, [edi]
/ ...mov [edi+edx], eax
//lea edi, [edi+4]
bukanstosd
. Haswell dan yang lebih baru dapat menyimpan toko yang diindeks dengan mikro. (Sandybridge / IvB akan menghapus laminasi juga.)Pada Intel Haswell dan sebelumnya,
adc
dancmovc
masing-masing 2 uops, dengan latensi 2c . (adc eax, [edi+edx]
masih belum dilaminasi di Haswell, dan diterbitkan sebagai 3 domain-fuse uops). Broadwell dan kemudian memungkinkan 3-input uops untuk lebih dari sekedar FMA (Haswell), membuatadc
dancmovc
(dan beberapa hal lainnya) instruksi single-uop, seperti mereka telah menggunakan AMD untuk waktu yang lama. (Ini adalah salah satu alasan AMD melakukannya dengan baik dalam tolok ukur GMP presisi panjang untuk waktu yang lama.) Bagaimanapun, loop internal Haswell harus 12 uops (kadang-kadang 1 stack-sync uop), dengan bottleneck front-end ~ 3c per iter kasus terbaik, abaikan tumpukan-sinkronisasi uops.Menggunakan
pop
tanpa menyeimbangkanpush
di dalam loop berarti loop tidak dapat berjalan dari LSD (loop stream detector) , dan harus dibaca kembali dari cache uop ke IDQ setiap waktu. Jika ada, itu adalah hal yang baik pada Skylake, karena loop 9 atau 10 uop tidak cukup masalah secara optimal pada 4 uops setiap siklus . Ini mungkin bagian dari mengapa menggantilodsd
denganpop
sangat membantu. (LSD tidak dapat mengunci uops karena itu tidak akan meninggalkan ruang untuk memasukkan tumpukan-sinkronisasi uop .) (BTW, pembaruan mikrokode menonaktifkan LSD sepenuhnya pada Skylake dan Skylake-X untuk memperbaiki erratum. Saya mengukur di atas sebelum mendapatkan pembaruan itu.)Saya memprofilkannya di Haswell, dan menemukan bahwa itu berjalan dalam 381,31 miliar siklus clock (terlepas dari frekuensi CPU, karena hanya menggunakan cache L1D, bukan memori). Throughput masalah front-end adalah 3,72 domain-leburan uops per jam, vs 3,70 untuk Skylake. (Tapi tentu saja instruksi per siklus turun ke 2,42 dari 2,87, karena
adc
dancmov
2 uops di Haswell.)push
untuk menggantistosd
mungkin tidak akan banyak membantu, karenaadc [esp + edx]
akan memicu tumpukan-sinkronisasi uop setiap kali. Dan akan memakan biaya satu byte untukstd
seterusnyalodsd
ke arah lain. (mov [edi], eax
/lea edi, [edi+4]
untuk menggantikanstosd
adalah kemenangan, pergi dari 32.909Mesin untuk 100M iter ke 31.954Mesel untuk iters 100M. Tampaknya diterjemahkanstosd
sebagai 3 uops, dengan uup store-address / store-data bukan micro-fused, jadipush
+ stack-sync mungkin masih lebih cepat daristosd
)Kinerja aktual ~ 322,47 miliar siklus untuk iterasi 1G dari 114 anggota badan bekerja hingga 2,824 siklus per iterasi loop dalam , untuk versi 105B cepat di Skylake. (Lihat
ocperf.py
output di bawah). Itu lebih lambat dari yang saya perkirakan dari analisis statis, tetapi saya mengabaikan overhead loop luar dan setiap tumpukan-sinkronisasi uopsPerf counters untuk
branches
danbranch-misses
menunjukkan bahwa loop dalam mispredicts sekali per loop luar (pada iterasi terakhir, ketika itu tidak diambil). Itu juga merupakan bagian dari waktu tambahan.Saya bisa menyimpan ukuran kode dengan membuat loop paling dalam memiliki latensi 3 siklus untuk jalur kritis, menggunakan
mov esi,eax
/sub eax,ebp
/cmovc eax, esi
/cmc
(2 + 2 + 3 + 1 = 8B) alih-alihlea esi, [eax - 1000000000]
/cmp ebp,eax
/cmovc
(6 + 2 + 3 = 11B ). Thecmov
/stosd
off jalur kritis. (Penambahan-edi uop daristosd
dapat berjalan secara terpisah dari toko, sehingga setiap iterasi memotong rantai ketergantungan pendek.) Dulu menyimpan 1B lain dengan mengubah instruksi ebp init darilea ebp, [ecx-1]
menjadimov ebp,eax
, tetapi saya menemukan bahwa memiliki kesalahanebp
tidak mengubah hasilnya. Ini akan membuat anggota badan menjadi persis == 1000000000 alih-alih membungkus dan memproduksi carry, tetapi kesalahan ini menyebar lebih lambat dari yang kita Fib () tumbuhkan, jadi ini terjadi tidak mengubah digit 1k terkemuka dari hasil akhir. Juga, saya pikir kesalahan itu dapat memperbaiki dirinya sendiri ketika kami baru saja menambahkan, karena ada ruang dalam dahan untuk menahannya tanpa meluap. Bahkan 1G + 1G tidak melimpahi integer 32-bit, sehingga akhirnya akan meresap ke atas atau terpotong.Versi latensi 3c adalah 1 ekstra uop, sehingga front-end dapat mengeluarkannya pada satu siklus per 2,75c pada Skylake, hanya sedikit lebih cepat daripada back-end yang dapat menjalankannya. (Di Haswell, itu akan menjadi total 13 uops karena masih menggunakan
adc
dancmov
, dan bottleneck di front-end di 3,25c per iter).Dalam praktiknya menjalankan faktor 1,18 lebih lambat pada Skylake (3,34 siklus per tungkai), daripada 3 / 2,5 = 1,2 yang saya prediksi untuk mengganti bottleneck front-end dengan bottleneck latency dari hanya melihat loop dalam tanpa melihat stack-sync uops. Karena stack-sync uops hanya merusak versi cepat (bottlenecked di front-end, bukan latency), tidak perlu banyak menjelaskannya. misal 3 / 2.54 = 1.18.
Faktor lain adalah bahwa versi latensi 3c dapat mendeteksi kesalahan perkiraan saat meninggalkan loop dalam saat jalur kritis masih mengeksekusi (karena front-end dapat maju dari back-end, membiarkan eksekusi out-of-order menjalankan loop- balik), sehingga hukuman salah saji efektif lebih rendah. Kehilangan siklus front-end tersebut memungkinkan back-end mengejar ketinggalan.
Jika bukan karena itu, kita mungkin bisa mempercepat
cmc
versi 3c dengan menggunakan cabang di lingkaran luar alih-alih penanganan tanpa cabang dari carry bawa -> edx dan offset. Prediksi cabang + eksekusi spekulatif untuk dependensi kontrol alih-alih ketergantungan data dapat membuat iterasi berikutnya mulai menjalankanadc
loop sementara uops dari loop dalam sebelumnya masih dalam penerbangan. Dalam versi branchless, alamat muat di loop dalam memiliki ketergantungan data pada CF dari yang terakhiradc
dari anggota gerak terakhir.Bottleneck versi loop dalam latensi 2c di front-end, sehingga back-end cukup banyak terus. Jika kode loop luar adalah latensi tinggi, front-end dapat melanjutkan mengeluarkan uops dari iterasi berikutnya dari loop dalam. (Tetapi dalam hal ini hal-hal loop luar memiliki banyak ILP dan tidak ada hal latensi tinggi, sehingga back-end tidak memiliki banyak hal yang harus dilakukan ketika mulai mengunyah uops dalam penjadwalan out-of-order sebagai input mereka menjadi siap).
( +- x %)
adalah standar-deviasi selama 4 berjalan untuk hitungan itu. Menarik bahwa ia menjalankan sejumlah instruksi. 924 miliar itu bukan kebetulan. Saya kira loop luar menjalankan total 924 instruksi.uops_issued
adalah jumlah domain-menyatu (relevan untuk bandwidth masalah front-end), sementara ituuops_executed
adalah jumlah-domain yang tidak digunakan (jumlah uops yang dikirim ke port eksekusi). Paket micro-fusion 2 uops domain yang tidak digunakan menjadi satu uop domain-menyatu, tetapi eliminasi gerak berarti bahwa beberapa uops domain-fusi tidak memerlukan port eksekusi. Lihat pertanyaan terkait untuk lebih lanjut tentang menghitung uops dan domain menyatu vs tidak terpakai. (Juga lihat tabel instruksi Agner Fog dan panduan uarch , dan tautan bermanfaat lainnya dalam wiki tag SO x86 ).Dari proses lain mengukur hal-hal yang berbeda: L1D cache misses sama sekali tidak signifikan, seperti yang diharapkan untuk membaca / menulis dua buffer 456B yang sama. Cabang loop-dalam salah memperkirakan satu kali per loop luar (ketika tidak diambil untuk meninggalkan loop). (Total waktu lebih tinggi karena komputer tidak sepenuhnya menganggur. Mungkin inti logis lainnya aktif beberapa saat, dan lebih banyak waktu dihabiskan dalam interupsi (karena frekuensi ruang-pengguna diukur lebih jauh di bawah 4,400GHz). Atau beberapa core aktif lebih banyak waktu, menurunkan max turbo. Saya tidak melacak
cpu_clk_unhalted.one_thread_active
untuk melihat apakah persaingan HT adalah masalah.)Kode saya mungkin berjalan dalam siklus yang lebih sedikit di Ryzen, yang dapat mengeluarkan 5 uops per siklus (atau 6 ketika beberapa dari mereka adalah instruksi 2-uop, seperti hal-hal AVX 256b pada Ryzen). Saya tidak yakin apa yang akan dilakukan front-end-nya
stosd
, yaitu 3 uops di Ryzen (sama dengan Intel). Saya pikir instruksi lain di loop dalam adalah latensi yang sama dengan Skylake dan semua single-uop. (Termasukadc eax, [edi+edx]
, yang merupakan keunggulan dibandingkan Skylake).Ini mungkin bisa secara signifikan lebih kecil, tapi mungkin 9x lebih lambat, jika saya menyimpan angka sebagai 1 angka desimal per byte . Menghasilkan barang bawaan dengan
cmp
dan menyesuaikan dengancmov
akan bekerja sama, tetapi melakukan 1/9 pekerjaan. 2 digit desimal per byte (basis-100, bukan BCD 4-bit dengan lambatDAA
) juga akan berfungsi, dandiv r8
/add ax, 0x3030
mengubah 0-99 byte menjadi dua digit ASCII dalam urutan pencetakan. Tapi 1 digit per byte tidak perludiv
sama sekali, hanya mengulang dan menambahkan 0x30. Jika saya menyimpan byte dalam urutan pencetakan, itu akan membuat loop ke-2 sangat sederhana.Menggunakan 18 atau 19 angka desimal per bilangan bulat 64-bit (dalam mode 64-bit) akan membuatnya berjalan sekitar dua kali lebih cepat, tetapi biaya ukuran kode yang signifikan untuk semua awalan REX, dan untuk konstanta 64-bit. 32-bit anggota badan dalam mode 64-bit mencegah menggunakan
pop eax
bukanlodsd
. Saya masih bisa menghindari awalan REX dengan menggunakanesp
sebagai register awal non-pointer (menukar penggunaanesi
danesp
), daripada menggunakanr8d
sebagai register ke-8.Jika membuat versi fungsi-panggilan, konversi ke 64-bit dan menggunakan
r8d
mungkin lebih murah daripada menyimpan / mengembalikanrsp
. 64-bit juga tidak dapat menggunakandec r32
pengodean satu byte (karena ini adalah awalan REX). Tapi kebanyakan saya akhirnya menggunakandec bl
yang 2 byte. (Karena saya memiliki konstanta dalam byte atasebx
, dan hanya menggunakannya di luar loop batin, yang berfungsi karena byte rendah dari konstanta adalah0x00
.)Versi kinerja tinggi
Untuk kinerja maksimum (bukan kode-golf), Anda ingin membuka gulungan lingkaran dalam sehingga menjalankan paling banyak 22 iterasi, yang merupakan pola pengambilan / tidak-ambil yang cukup singkat untuk dilakukan oleh prediktor cabang dengan baik. Dalam eksperimen saya,
mov cl, 22
sebelum satu.inner: dec cl/jnz .inner
loop memiliki sangat sedikit mispredict (seperti 0,05%, jauh lebih sedikit dari satu per putaran penuh dari loop dalam), tetapimov cl,23
mispredict mulai dari 0,35 hingga 0,6 kali per loop dalam.46
sangat buruk, mispredicting ~ 1,28 kali per loop dalam (128M kali untuk iterasi loop luar 100M).114
mispredicted tepat sekali per loop dalam, sama seperti yang saya temukan sebagai bagian dari loop Fibonacci.Saya jadi penasaran dan mencobanya, membuka gulungan lingkaran dalam dengan 6
%rep 6
(karena itu membagi 114 secara merata). Itu sebagian besar menghilangkan kesalahan cabang. Saya membuatedx
negatif dan menggunakannya sebagai offset untukmov
toko, jadiadc eax,[edi]
bisa tetap menyatu secara mikro. (Dan supaya aku bisa menghindaristosd
). Saya menariklea
untuk memperbaruiedi
keluar dari%rep
blok, jadi itu hanya melakukan satu pointer-pembaruan per 6 toko.Saya juga menyingkirkan semua hal-hal register parsial di loop luar, meskipun saya tidak berpikir itu penting. Mungkin sedikit membantu memiliki CF di ujung loop luar tidak tergantung pada ADC akhir, sehingga beberapa loop dalam loop dapat dimulai. Kode loop luar mungkin dapat dioptimalkan sedikit lebih, karena
neg edx
adalah hal terakhir yang saya lakukan, setelah menggantixchg
dengan hanya 2mov
instruksi (karena saya sudah memiliki 1), dan mengatur ulang rantai dep bersama dengan menjatuhkan 8-bit daftar barang.Ini adalah sumber NASM dari hanya loop Fibonacci. Ini pengganti drop-in untuk bagian versi asli.
Kinerja:
Itu untuk Fib yang sama (1G), menghasilkan output yang sama dalam 62,3 detik, bukan 73 detik. (273.146G siklus, vs. 322.467G. Karena semuanya menyentuh dalam cache L1, siklus clock inti benar-benar yang perlu kita lihat.)
Perhatikan jumlah total jauh lebih
uops_issued
rendah, jauh di bawahuops_executed
jumlah. Itu berarti banyak dari mereka adalah mikro-menyatu: 1 uop dalam domain menyatu (masalah / ROB), tetapi 2 uops dalam domain yang tidak digunakan (penjadwal / unit eksekusi)). Dan itu sedikit yang dihilangkan pada tahap masalah / ganti nama (sepertimov
register copying, atauxor
-zeroing, yang perlu dikeluarkan tetapi tidak memerlukan unit eksekusi). Uops yang dieliminasi akan mengacaukan hitungan dengan cara lain.branch-misses
turun ke ~ 400k, dari 1G, jadi membuka gulungan bekerja.resource_stalls.any
signifikan sekarang, yang berarti front-end bukan hambatan lagi: sebaliknya back-end semakin di belakang dan membatasi front-end.idq_uops_not_delivered.core
hanya menghitung siklus di mana front-end tidak mengirimkan up, tetapi back-end tidak terhenti. Itu bagus dan rendah, menunjukkan beberapa kemacetan front-end.Fakta menyenangkan: versi python menghabiskan lebih dari setengah waktunya untuk membagi dengan 10 daripada menambahkan. (Mengganti
a/=10
dengana>>=64
mempercepatnya lebih dari faktor 2, tetapi mengubah hasilnya karena pemotongan biner! = Pemotongan desimal.)Versi asm saya tentu saja dioptimalkan secara khusus untuk ukuran masalah ini, dengan loop iteration-menghitung hard coded. Bahkan menggeser angka presisi yang sewenang-wenang akan menyalinnya, tetapi versi saya hanya bisa membaca dari offset untuk dua iterasi berikutnya untuk melewati bahkan itu.
Saya membuat profil versi python (64-bit python2.7 di Arch Linux):
Angka dalam (parens) adalah berapa kali penghitung perf sedang disampel. Ketika melihat lebih banyak penghitung daripada dukungan HW, perf berputar antara penghitung berbeda dan ekstrapolasi. Itu benar-benar baik untuk jangka panjang dari tugas yang sama.
Jika saya berlari
perf
setelah mengatur sysctlkernel.perf_event_paranoid = 0
(atau menjalankanperf
sebagai root), itu akan mengukur4.400GHz
.cycles:u
tidak menghitung waktu yang dihabiskan dalam interupsi (atau panggilan sistem), hanya siklus ruang pengguna. Desktop saya hampir sepenuhnya idle, tetapi ini tipikal.sumber
Haskell,
8361 byteOutput ( F 1000000000 , F 1000000001 ). Di laptop saya, ia mencetak dengan benar paren kiri dan 1000 digit pertama dalam 133 detik, menggunakan memori 1,35 GiB.
Bagaimana itu bekerja
Perulangan Fibonacci dapat diselesaikan dengan menggunakan matriks eksponensial:
[ F i - 1 , F i ; F i , F i + 1 ] = [0, 1; 1, 1] saya ,
dari mana kita memperoleh identitas ini:
[ 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 .
The
p
Toedjoe menghitung fungsi ( F i + j , F i + j + 1 ) diberikan ( F i , F i + 1 ) dan ( F j , F j + 1 ). Menulisf n
untuk ( F i , F i +1 ), kami memilikip (f i) (f j)
=f (i + j)
.Kemudian,
(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)
,dan kami pasang
f 1
=(1,1)
.sumber
Mathematica, 15
34byteFibonacci
itu sendiri mengambil ~ 6s di komputer saya. Dan 95 (+/- 5) untuk tampilan depan untuk itu.1000 digit pertama (34 byte):
⌊Fibonacci@1*^9/1*^208986640⌋&
Lebih lama tetapi lebih cepat
ToString@Fibonacci@1*^9~StringTake~1000&
:sumber
div
). Saya berhenti karena orang mungkin sudah selesai melihat pertanyaan ini pada saat saya memiliki fungsi golf yang baik yang melakukan semua pekerjaan itu. Tetapi tampaknya kekerasan dapat bekerja, seperti yang ditunjukkan beberapa jawaban.Python 2, 70 byte
Ini berjalan dalam 18 menit dan 31 detik pada laptop saya, menghasilkan 1000 digit yang benar diikuti oleh
74100118580
(digit berikut yang benar adalah74248787892
).sumber
div
lingkaran untuk membuat 9 angka desimal per-chunk. Bawa selama menambahkan dengan cmp / cmov dan 2xADD bukan ADC.Haskell , 78 byte
Cobalah online!
Butuh 48 detik di TIO. Rumus rekursif yang sama dengan jawaban Python saya , tetapi tanpa memotong.
Konstanta
2143923439
adalah10**9-1
, dibalik dalam biner, dan dengan tambahan 1 di akhir. Iterasi melalui digit biner secara terbalik mensimulasikan iterasi melalui digit biner10**9-1
. Tampaknya lebih pendek untuk meng-hardcode ini daripada menghitungnya.sumber
Haskell ,
202184174173170168164162 byteCobalah online!
Penjelasan
Ini menggunakan cara yang agak cepat untuk menghitung angka fibonacci. Fungsi
l
mengambil dua fibonacci angka dan menghitung angka fibonacci 10 kemudian, sementaraf
mengambil n th dan n + 1 th angka fibonacci dan menghitung 2n + 20 th dan 2n + 21 nomor th fibonacci. Saya rantai mereka agak sembarangan untuk mendapatkan 1 miliar dan ambil 1000 digit pertama.sumber
Haskell, 81 byte
Penjelasan
f n
secara rekursif menghitung angkan
fibonacci menggunakan rekurensi dari jawaban xnor dengan eliminasi common-subexpression. Tidak seperti solusi lain yang telah diposting, yang menggunakan O (log (n)) multiplikasi, kami memiliki rekursi kedalaman O (log (n)) dengan faktor percabangan 2, untuk kompleksitas O (n) multiplikasi.Namun, semuanya tidak hilang! Karena hampir semua panggilan akan berada di dekat bagian bawah pohon rekursi, kita dapat menggunakan aritmatika asli cepat jika mungkin dan menghindari banyak manipulasi bignum besar. Itu mengeluarkan jawaban dalam beberapa menit di kotak saya.
sumber
T-SQL,
422 414453 byte (Terverifikasi, sekarang bersaing!)EDIT 2 : Diubah menjadi , Memperoleh beberapa byte tetapi meningkatkan kecepatan yang cukup untuk menyelesaikan hingga 1 miliar! Selesai dalam 45 jam 29 menit , memverifikasi terhadap string yang diberikan, dan menampilkan 8 karakter tambahan (yang mungkin atau mungkin tidak benar karena kesalahan pembulatan).
INT BIGINT
DECIMAL(37,0)
T-SQL tidak memiliki dukungan asli "angka besar", jadi saya harus menggulung penambah angka besar berbasis teks saya sendiri menggunakan string 1008-karakter:
Inilah versi yang diformat dengan komentar:
Pada dasarnya saya secara manual memanipulasi 1008 karakter string berisi nol yang mewakili dua variabel Fibonacci saya,
@a
dan@
.Saya menambahkannya
8 1836 digit sekaligus, dengan menanggalkan 36 digit terakhir, mengonversinya menjadi tipe numerik yang dapat diatur (DECIMAL(37,0)
), menambahkannya, lalu menghancurkannya kembali menjadi string panjang lainnya@c
. Saya kemudian "memutar"@a
dan@
dengan memindahkan 36 digit terakhir ke depan, dan mengulangi prosesnya. 28 rotasi * 36 digit mencakup semua 1008. Saya harus "membawa satu" secara manual.Setelah nomor kami mulai melebihi panjang string saya, saya "bergeser ke kiri" dan kami mulai kehilangan beberapa ketepatan, tetapi kesalahannya masih dalam karakter tambahan saya.
Saya mencoba menggunakan tabel SQL yang penuh dengan INT dan BIGINT, dengan logika yang sama, dan secara dramatis lebih lambat. Aneh.
sumber
PARI / GP, 45 byte
Entah bagaimana
\p1000
tidak cukup. Ini tidak bekerja dengan sistem 32 bit. Pembagian akhir adalah untuk menghindari titik desimal dalam notasi ilmiah.sumber
Pari / GP , 15 + 5 = 20 byte
Jalankan dengan opsi baris perintah
-s1g
untuk mengalokasikan 1 Gbytes memori.sumber
Ruby, 63 byte
***, aku buruk dalam golf ruby; tapi kelas BigInt melakukan keajaiban untuk hal semacam ini. Kami menggunakan algoritma yang sama dengan Anders Kaseorg.
sumber