x86 32 bit makine kodu (Linux sistem çağrılarıyla birlikte): 106 105 bayt
changelog: hızlı sürümde bir bayt kaydetti, çünkü bir off-one sabiti Fib (1G) sonucunu değiştirmiyor.
Ya da versiyonu (Skylake üzerinde)% 18 daha yavaş 102 bayt (kullanarak mov
/ sub
/ cmc
yerine lea
/ ' cmp
, iç döngü içinde, en taşıma-out ve sarma oluşturmak için 10**9
yerine 2**32
). Veya en içteki döngüde taşıma işleminde bir dal ile ~ 5.3x daha yavaş bir sürüm için 101 bayt. (% 25,4 şube-yanlış tahmin oranı ölçtüm!)
Ya da baştaki sıfıra izin verilirse 104/101 bayt. (Çıktının 1 basamağını atlayarak sabit koda 1 bayt alır, bu Fib için gerekli olan şeydir (10 ** 9).
Ne yazık ki, TIO'nun NASM modu -felf32
derleyici bayrağında yok sayar gibi görünüyor . İşte yine de , tam kaynak kodumla, yorumların içindeki deneysel fikirlerin karışıklığıyla bir bağlantı .
Bu tam bir programdır . İlk 1000 basamağı Fib (10 ** 9) ve ardından bazı ekstra basamağı (son birkaç tanesi yanlış olanı), ardından bazı çöp baytlarını (yeni satır hariç) yazdırır. Çöplerin çoğu ASCII değildir, bu nedenle boru çekmek isteyebilirsiniz cat -v
. Yine de terminal emülatörümü (KDE konsole
) kırmıyor. "Çöp baytları" Fib'i (999999999) saklıyor. Zaten -1024
bir kayıt defterinde bulundum , bu yüzden uygun boyuttan 1024 bayt yazdırmak daha ucuzdu.
Sadece makine kodunu sayıyorum (statik çalıştırılabilirimin metin bölümünün boyutu), onu ELF tarafından çalıştırılabilir kılan kabartmayı değil. ( Çok küçük ELF dosyaları çalıştırılabilir , ancak bununla uğraşmak istemedim). BSS yerine yığın bellek kullanmak daha kısa olduğu için herhangi bir metadata bağlı olmadığım için ikili sistemde başka hiçbir şeyi saymamayı haklı çıkarabilirim. (Soyulmuş bir statik ikili sayı üretme normal şekilde 340 baytlık bir ELF çalıştırılabilir hale getirir.)
Bu kodun dışında C'den çağırabileceğiniz bir işlev oluşturabilirsiniz. Yığın işaretçisini (belki bir MMX kayıt defterinde olabilir) ve diğer bazı ek yükleri kaydetmek / geri yüklemek birkaç haneye mal olur, ancak dize ile dönerek de baytları kurtarır. bellekte, write(1,buf,len)
sistem çağrısı yapmak yerine . Makine kodunda golf oynamanın bana burada biraz durgunluk kazandırması gerektiğini düşünüyorum, çünkü hiç kimse yerel dilde geniş bir hassasiyet olmadan herhangi bir dilde bir cevap bile yayınlamamıştı, ancak bunun bir işlevsel versiyonunun tümüyle yeniden golf oynamadan 120 bayt altında olması gerektiğini düşünüyorum. şey.
Algoritma:
kaba kuvvet a+=b; swap(a,b)
, sadece öncü> = 1017 ondalık basamak tutmak için gerektiği gibi kesiliyor. Bilgisayarımda 1dk13'lerde çalışıyor (veya 322.47 milyar saat döngüsü + -% 0.05) (ve birkaç ekstra kod baytında% birkaç daha hızlı olabilir veya loop unroll işleminden çok daha büyük kod boyutu ile 62 saniyeye kadar olabilir. akıllı matematik, sadece daha az ek yük ile aynı işi yapıyorum). Bu dayanıyor @ AndersKaseorg Python uygulaması bilgisayarım (4.4GHz Skylake i7-6700k) üzerinde 12min35s çalışır. Her iki sürümde de L1D önbellek kaçırmadığı için DDR4-2666 benim için önemli değil.
Python'un aksine genişletilmiş sayıları, ondalık basamakları serbest bırakan bir biçimde saklarım . 32-bit tamsayı başına 9 ondalık basamaklı grupları saklarım, böylece bir işaretçi ofseti düşük 9 basamağı atar. Bu, 10 milyarlık bir güç olan 1 milyar taban kazanıyor. (Bu zorluğun 1 milyarda bir Fibonacci sayısına ihtiyaç duyması tam bir tesadüf, ancak beni iki bayt ile iki ayrı sabit arasında kurtarıyor.)
GMP terminolojisini takiben , genişletilmiş hassasiyetli bir sayının her bir 32-bit öbekine "uzuv" denir. Ekleme yapılırken gerçekleştirilmenin 1e9 ile karşılaştırmalı olarak manuel olarak üretilmesi gerekir, ancak daha sonra normalde bir sonraki uzuv için olağan ADC
talimatın girişi olarak kullanılır . (Ayrıca [0..999999999]
2 ^ 32 ~ = 4.295e9 yerine menzile manuel olarak sarmam gerekiyor. Karşılaştırmadan elde edilen sonucu kullanarak bunu dalsız olarak lea
+ ile yapıyorum cmov
.)
Son uzuv sıfır olmayan bir çıkıntı ürettiğinde, dış döngünün sonraki iki yinelemesi normalden yüksek olan 1 uzuvdan okunur ancak yine de aynı yere yazılır. Bu, memcpy(a, a+4, 114*4)
1 uzuv tarafından sağa kaymaya benzer, ancak sonraki iki toplama döngüsünün bir parçası olarak yapılır. Bu, her ~ 18 tekrarlamada gerçekleşir.
Boyut tasarrufu ve performans için kesmek:
Gibi olağan şeyler lea ebx, [eax-4 + 1]
yerine mov ebx, 1
bunu biliyorum zaman eax=4
. Ve yavaşlığın sadece küçük bir etkisi loop
olduğu yerlerde kullanmak .LOOP
adc
İç döngüdeki tamponun başlangıcına yazmaya devam ederken okuduğumuz göstericileri uzaklaştırarak, 1 ekstremite için ücretsiz kesin . Biz okuduk [edi+edx]
ve yazıyoruz [edi]
. Böylece hedef için bir okuma-yazma ofseti alabiliriz edx=0
ya 4
da alabiliriz. Bunu iki ardışık yineleme için yapmalıyız, önce ikisini de, sonra da yalnızca dst'i dengeleyerek. esp&4
İşaretçileri arabelleklerin ön tarafına sıfırlamadan önce bakarak 2. vakayı saptarız ( &= -1024
arabellekleri hizalı olduğu için kullanarak ). Koddaki yorumları görün.
Linux işlem başlatma ortamı (statik bir çalıştırılabilir için) çoğu kaydı sıfırlar ve esp
/ altındaki yığın belleği rsp
sıfırlanır. Programım bundan faydalanıyor. Bunun çağrılabilir işlevli bir sürümünde (ayrılmamış yığının kirlenebileceği), sıfırlanmış bellek için BSS'yi kullanabilirim (işaretçileri ayarlamak için belki de 4 bayt pahasına). Sıfırlama edx
2 bayt alır. X86-64 System V ABI bunların her ikisini de garanti etmiyor, ancak Linux uygulamasının sıfır olması (çekirdeğin dışına bilgi sızıntısını önlemek için). Dinamik olarak bağlı bir işlemde, /lib/ld.so
önce çalışır _start
ve yazmaçları sıfır bırakmaz (ve yığın işaretçisinin altındaki bellekte çöp).
Ben tutmak -1024
içinde ebx
döngüler kullanılması dışından için. bl
Sıfır ile biten iç döngüler için bir sayaç olarak kullanın (bu düşük bayttır -1024
, bu nedenle döngü dışında kullanım için sabiti geri yükler). Intel Haswell ve sonraki sürümlerde düşük kayıtlar için kısmi kayıt birleştirme cezaları yoktur (ve aslında bunları ayrı olarak yeniden adlandırmazlar) , bu nedenle AMD'de olduğu gibi tam kayıt defterine bağımlılık vardır (burada sorun değil). Nehalem ve daha önce, bu birleşme sırasında kısmi sicil tezgahları olan korkunç olurdu. Kısmi xor
regs yazdığım ve sonra sıfırlamadan veyamovzx
, genellikle bazı önceki kodların üst baytları sıfırladığını bildiğim için ve AMD ve Intel SnB ailesinde yine iyi, ancak Intel öncesi Sandybridge'de yavaş.
1024
Stdout ( sub edx, ebx
) ' a yazmak için bayt sayısını kullanıyorum , bu yüzden programım Fibonacci rakamlarından sonra bazı çöp baytlarını yazdırıyor, çünkü mov edx, 1000
maliyeti daha fazla bayt.
(kullanılmıyor) adc ebx,ebx
1 bayt vs tasarruf Ebx = CF almak için EBX = 0 ile setc bl
.
dec
/ jnz
bir adc
döngü içinde adc
Intel Sandybridge ve daha sonra bayrakları okuduğunda kısmi bayrak durmasına neden olmadan CF'yi korur . Daha önceki işlemcilerde kötü , ancak Skylake'de AFAIK ücretsiz. Ya da en kötüsü, fazladan bir şey.
Aşağıdaki belleği esp
dev bir kırmızı bölge olarak kullanın . Bu, tam bir Linux programı olduğundan, herhangi bir sinyal işleyicisi yüklemediğimi ve başka hiçbir şeyin eşzamansız olarak kullanıcı alanı yığın hafızasına zarar vermeyeceğini biliyorum. Diğer işletim sistemlerinde durum böyle olmayabilir.
Yararlanın yığın-motor kullanarak sorunu bant genişliğini uop kurtarmak için pop eax
yerine (1 uop + ara sıra yığını senkron uop) lodsd
(Haswell / Skylake, IVB üzerinde 3 2 UOPs ve önceki göre Agner Fog'un talimat tabloları )). IIRC, bu çalışma süresini yaklaşık 83 saniyeden 73'e düşürdü. Src ve dst arabellekleri arasındaki farkı olduğu mov
gibi, indekslenmiş bir adresleme moduyla a kullanarak aynı hızı elde edebilirim . (Fibonacci yinelemeleri için swapping src ve dst'in bir kayması olarak ofset registerını reddetmek zorunda kalacak şekilde iç döngü dışındaki kodu daha karmaşık hale getirir.) Daha fazlası için aşağıdaki "performans" bölümüne bakın.mov eax, [edi+ebp]
ebp
ilk yinelemeyi herhangi stc
bir 1
yerde bir belleğe kaydetmek yerine, bir geçiş (bir bayt ) vererek diziyi başlatın . Yorumlarda belgelenen başka probleme özel şeyler de var.
NASM listesi (makine kodu + kaynak) ile oluşturulmuştur nasm -felf32 fibonacci-1G.asm -l /dev/stdout | cut -b -28,$((28+12))- | sed 's/^/ /'
. (Sonra, yorumlanan öğelerin bazı bloklarını elden kaldırdım, bu nedenle satır numaralandırmada boşluklar var.) Baştaki sütunları çıkarmak için YASM veya NASM'e besleyebilmeniz için kullanın cut -b 27- <fibonacci-1G.lst > fibonacci-1G.asm
.
1 machine global _start
2 code _start:
3 address
4 00000000 B900CA9A3B mov ecx, 1000000000 ; Fib(ecx) loop counter
5 ; lea ebp, [ecx-1] ; base-1 in the base(pointer) register ;)
6 00000005 89CD mov ebp, ecx ; not wrapping on limb==1000000000 doesn't change the result.
7 ; It's either self-correcting after the next add, or shifted out the bottom faster than Fib() grows.
8
42
43 ; mov esp, buf1
44
45 ; mov esi, buf1 ; ungolfed: static buffers instead of the stack
46 ; mov edi, buf2
47 00000007 BB00FCFFFF mov ebx, -1024
48 0000000C 21DC and esp, ebx ; alignment necessary for convenient pointer-reset
49 ; sar ebx, 1
50 0000000E 01DC add esp, ebx ; lea edi, [esp + ebx]. Can't skip this: ASLR or large environment can put ESP near the bottom of a 1024-byte block to start with
51 00000010 8D3C1C lea edi, [esp + ebx*1]
52 ;xchg esp, edi ; This is slightly faster. IDK why.
53
54 ; It's ok for EDI to be below ESP by multiple 4k pages. On Linux, IIRC the main stack automatically extends up to ulimit -s, even if you haven't adjusted ESP. (Earlier I used -4096 instead of -1024)
55 ; After an even number of swaps, EDI will be pointing to the lower-addressed buffer
56 ; This allows a small buffer size without having the string step on the number.
57
58 ; registers that are zero at process startup, which we depend on:
59 ; xor edx, edx
60 ;; we also depend on memory far below initial ESP being zeroed.
61
62 00000013 F9 stc ; starting conditions: both buffers zeroed, but carry-in = 1
63 ; starting Fib(0,1)->0,1,1,2,3 vs. Fib(1,0)->1,0,1,1,2 starting "backwards" puts us 1 count behind
66
67 ;;; register usage:
68 ;;; eax, esi: scratch for the adc inner loop, and outer loop
69 ;;; ebx: -1024. Low byte is used as the inner-loop limb counter (ending at zero, restoring the low byte of -1024)
70 ;;; ecx: outer-loop Fibonacci iteration counter
71 ;;; edx: dst read-write offset (for "right shifting" to discard the least-significant limb)
72 ;;; edi: dst pointer
73 ;;; esp: src pointer
74 ;;; ebp: base-1 = 999999999. Actually still happens to work with ebp=1000000000.
75
76 .fibonacci:
77 limbcount equ 114 ; 112 = 1006 decimal digits / 9 digits per limb. Not enough for 1000 correct digits, but 114 is.
78 ; 113 would be enough, but we depend on limbcount being even to avoid a sub
79 00000014 B372 mov bl, limbcount
80 .digits_add:
81 ;lodsd ; Skylake: 2 uops. Or pop rax with rsp instead of rsi
82 ; mov eax, [esp]
83 ; lea esp, [esp+4] ; adjust ESP without affecting CF. Alternative, load relative to edi and negate an offset? Or add esp,4 after adc before cmp
84 00000016 58 pop eax
85 00000017 130417 adc eax, [edi + edx*1] ; read from a potentially-offset location (but still store to the front)
86 ;; jz .out ;; Nope, a zero digit in the result doesn't mean the end! (Although it might in base 10**9 for this problem)
87
88 %if 0 ;; slower version
;; could be even smaller (and 5.3x slower) with a branch on CF: 25% mispredict rate
89 mov esi, eax
90 sub eax, ebp ; 1000000000 ; sets CF opposite what we need for next iteration
91 cmovc eax, esi
92 cmc ; 1 extra cycle of latency for the loop-carried dependency. 38,075Mc for 100M iters (with stosd).
93 ; not much worse: the 2c version bottlenecks on the front-end bottleneck
94 %else ;; faster version
95 0000001A 8DB0003665C4 lea esi, [eax - 1000000000]
96 00000020 39C5 cmp ebp, eax ; sets CF when (base-1) < eax. i.e. when eax>=base
97 00000022 0F42C6 cmovc eax, esi ; eax %= base, keeping it in the [0..base) range
98 %endif
99
100 %if 1
101 00000025 AB stosd ; Skylake: 3 uops. Like add + non-micro-fused store. 32,909Mcycles for 100M iters (with lea/cmp, not sub/cmc)
102 %else
103 mov [edi], eax ; 31,954Mcycles for 100M iters: faster than STOSD
104 lea edi, [edi+4] ; Replacing this with ADD EDI,4 before the CMP is much slower: 35,083Mcycles for 100M iters
105 %endif
106
107 00000026 FECB dec bl ; preserves CF. The resulting partial-flag merge on ADC would be slow on pre-SnB CPUs
108 00000028 75EC jnz .digits_add
109 ; bl=0, ebx=-1024
110 ; esi has its high bit set opposite to CF
111 .end_innerloop:
112 ;; after a non-zero carry-out (CF=1): right-shift both buffers by 1 limb, over the course of the next two iterations
113 ;; next iteration with r8 = 1 and rsi+=4: read offset from both, write normal. ends with CF=0
114 ;; following iter with r8 = 1 and rsi+=0: read offset from dest, write normal. ends with CF=0
115 ;; following iter with r8 = 0 and rsi+=0: i.e. back to normal, until next carry-out (possible a few iters later)
116
117 ;; rdi = bufX + 4*limbcount
118 ;; rsi = bufY + 4*limbcount + 4*carry_last_time
119
120 ; setc [rdi]
123 0000002A 0F92C2 setc dl
124 0000002D 8917 mov [edi], edx ; store the carry-out into an extra limb beyond limbcount
125 0000002F C1E202 shl edx, 2
139 ; keep -1024 in ebx. Using bl for the limb counter leaves bl zero here, so it's back to -1024 (or -2048 or whatever)
142 00000032 89E0 mov eax, esp ; test/setnz could work, but only saves a byte if we can somehow avoid the or dl,al
143 00000034 2404 and al, 4 ; only works if limbcount is even, otherwise we'd need to subtract limbcount first.
148 00000036 87FC xchg edi, esp ; Fibonacci: dst and src swap
149 00000038 21DC and esp, ebx ; -1024 ; revert to start of buffer, regardless of offset
150 0000003A 21DF and edi, ebx ; -1024
151
152 0000003C 01D4 add esp, edx ; read offset in src
155 ;; after adjusting src, so this only affects read-offset in the dst, not src.
156 0000003E 08C2 or dl, al ; also set r8d if we had a source offset last time, to handle the 2nd buffer
157 ;; clears CF for next iter
165 00000040 E2D2 loop .fibonacci ; Maybe 0.01% slower than dec/jnz overall
169 to_string:
175 stringdigits equ 9*limbcount ; + 18
176 ;;; edi and esp are pointing to the start of buffers, esp to the one most recently written
177 ;;; edi = esp +/- 2048, which is far enough away even in the worst case where they're growing towards each other
178 ;;; update: only 1024 apart, so this only works for even iteration-counts, to prevent overlap
180 ; ecx = 0 from the end of the fib loop
181 ;and ebp, 10 ; works because the low byte of 999999999 is 0xff
182 00000042 8D690A lea ebp, [ecx+10] ;mov ebp, 10
183 00000045 B172 mov cl, (stringdigits+8)/9
184 .toascii: ; slow but only used once, so we don't need a multiplicative inverse to speed up div by 10
185 ;add eax, [rsi] ; eax has the carry from last limb: 0..3 (base 4 * 10**9)
186 00000047 58 pop eax ; lodsd
187 00000048 B309 mov bl, 9
188 .toascii_digit:
189 0000004A 99 cdq ; edx=0 because eax can't have the high bit set
190 0000004B F7F5 div ebp ; edx=remainder = low digit = 0..9. eax/=10
197 0000004D 80C230 add dl, '0'
198 ; stosb ; clobber [rdi], then inc rdi
199 00000050 4F dec edi ; store digits in MSD-first printing order, working backwards from the end of the string
200 00000051 8817 mov [edi], dl
201
202 00000053 FECB dec bl
203 00000055 75F3 jnz .toascii_digit
204
205 00000057 E2EE loop .toascii
206
207 ; Upper bytes of eax=0 here. Also AL I think, but that isn't useful
208 ; ebx = -1024
209 00000059 29DA sub edx, ebx ; edx = 1024 + 0..9 (leading digit). +0 in the Fib(10**9) case
210
211 0000005B B004 mov al, 4 ; SYS_write
212 0000005D 8D58FD lea ebx, [eax-4 + 1] ; fd=1
213 ;mov ecx, edi ; buf
214 00000060 8D4F01 lea ecx, [edi+1] ; Hard-code for Fib(10**9), which has one leading zero in the highest limb.
215 ; shr edx, 1 ; for use with edx=2048
216 ; mov edx, 100
217 ; mov byte [ecx+edx-1], 0xa;'\n' ; count+=1 for newline
218 00000063 CD80 int 0x80 ; write(1, buf+1, 1024)
219
220 00000065 89D8 mov eax, ebx ; SYS_exit=1
221 00000067 CD80 int 0x80 ; exit(ebx=1)
222
# next byte is 0x69, so size = 0x69 = 105 bytes
Muhtemelen bundan biraz daha fazla bayta golf oynamak için oda vardır, ancak 2 gün boyunca bunun için en az 12 saat geçirdim. Hıztan fedakarlık etmek istemiyorum, yeterince hızlı olmasına rağmen ve hızı düşürecek şekilde küçültmek için yer var . Gönderme nedenimin bir parçası kaba kuvvet bir asm sürümünü ne kadar hızlı yapabildiğimi gösteriyor. Eğer biri gerçekten asgari boyutta ama belki de 10 kat daha yavaş (örneğin, bayt başına 1 hane) gitmek istiyorsa, bunu bir başlangıç noktası olarak kopyalamaktan çekinmeyin.
Elde edilen çalıştırılabilir (from yasm -felf32 -Worphan-labels -gdwarf2 fibonacci-1G.asm && ld -melf_i386 -o fibonacci-1G fibonacci-1G.o
) 340B (sıyırılmış):
size fibonacci-1G
text data bss dec hex filename
105 0 0 105 69 fibonacci-1G
Verim
İç adc
döngü, Skylake'de 10 kaynaşık alan adıdır (her ~ 128 baytta +1 yığın senkronizasyonu), bu nedenle en iyi ön uç verimine sahip (yığın senkronizasyonu ihmallerini görmezden) Skylake'de ~ 2,5 döngü başına bir değer verebilir. . Kritik yol gecikmesi adc
-> cmp
- -> sonraki yinelemenin adc
döngü taşıma bağımlılık zinciri için 2 döngüdür, bu nedenle darboğaz yineleme başına ~ 2,5 döngü ön uç sayı sınırı olmalıdır.
adc eax, [edi + edx]
yürütme bağlantı noktaları için 2 kaynaşık olmayan etki alanı kümesidir: load + ALU. Kod çözücüler içinde mikro-kaynaşmalar meydana gelir (1 kaynaşık alanlı etki alanı), ancak sorunlu aşamada laminatlanır, Dizine alınmış adresleme modundan dolayı Haswell / Skylake'de bile, 2 kaynaşık alanlı etki alanına katılır . Sanırım mikro kaynaşık kalacaktı add eax, [edi + edx]
, ama indekslenmiş adresleme modlarını saklamak mikro kaynaşmış zaten 3 girişe sahip olan girişlerde (bayrak, bellek ve hedef) işe yaramaz. Yazarken, olumsuz bir performansa sahip olmayacağını düşünüyordum, ama yanılmışım. Bu kesme işleminin yolu edx
0 veya 4 olsun , her zaman iç çevrimi yavaşlatır .
Deposunu ayarlamak için edi
kullanarak ve yazarak dst için okuma-yazma ofsetini kullanmak daha hızlı olacaktır edx
. Yani adc eax, [edi]
/ ... / mov [edi+edx], eax
/ lea edi, [edi+4]
yerine stosd
. Haswell ve daha sonra endekslenmiş bir mağazayı mikro sigortalı tutabilir. (Sandybridge / IvB de buna katılmazdı.)
Intel Haswell ve daha önceki sürümlerde adc
ve cmovc
2c gecikmeyle her biri 2 uops . ( adc eax, [edi+edx]
Haswell'de hala lamine edilmemiştir ve 3 kaynaşık alan adı birimi olarak yayınlanmaktadır). Broadwell ve daha sonra , uzun süredir AMD'de olduğu gibi, tek girişli talimatlar adc
ve cmovc
(ve birkaç başka şey) FMA'dan (Haswell) daha fazlası için 3-girişe izin verir . (Bu, AMD'nin uzun süredir hassas GMP testlerinde iyi bir performans göstermesinin bir nedenidir.) Her neyse, Haswell'in iç halkası 12 opss (zaman zaman +1 yığın senkronizasyonu per) olmalıdır, en iyi durumda, yığın senkronizasyon uops göz ardı ederek.
Döngü içinde pop
bir balans olmadan kullanmak push
, döngünün LSD'den (döngü akış detektörü) çalışamayacağı ve her seferinde uop önbellekten IDQ'ya yeniden okunması gerektiği anlamına gelir. Bir şey varsa, Skylake'de iyi bir şey, çünkü 9 ya da 10 uop döngü her döngüde 4 uop'ta optimal olarak sorun çıkarmaz . Bu muhtemelen değiştirilmesi neden parçasıdır lodsd
ile pop
çok yardımcı oldu. (Bir yığın senkronizasyonu eklemek için odadan çıkmadığı için LSD aygıtları kilitleyemez .) (Bir mikro kod güncellemesi olan BTW, bir hata düzeltmek için LSD'yi tamamen Skylake ve Skylake-X üzerinde devre dışı bırakır. bu güncellemeyi almadan önce yukarıda.)
Haswell'de profilini çıkardım ve 381.31 milyar saat döngüsünde çalıştığını öğrendim (CPU frekansından bağımsız olarak, yalnızca L1D önbelleğini kullandığından bellek kullanmıyor). Ön uç sorun çıktısı, saat başına 3,72 sigorta alanı etki alanı, Skylake için 3,70'tir. (Çünkü Ama tabii döngüsü başına talimatları, 2.87 ila 2.42 aşağı oldu adc
ve cmov
Haswell 2 UOPs bulunmaktadır.)
push
stosd
muhtemelen değiştirmek o kadar yardımcı olmaz, çünkü adc [esp + edx]
her seferinde bir senkronizasyon senkronizasyonu tetiklerdi. Ve bir bayta mal olur, std
yani lodsd
diğer yöne gider. ( mov [edi], eax
/ lea edi, [edi+4]
yerine stosd
koyma, 100 milyon iters için 32.909Mcycles'tan, 100 milyon iters için 31,954Mycle'a kadar olan bir galibiyettir. Görünüşe göre stosd
, mağaza adresi / mağaza verisi mikro-kaynaşık değil, yani push
+ yığın senkronizasyonu ile 3 uop olarak çözülür) uops hala daha hızlı olabilir stosd
)
114B uzuvların 1G tekrarlamaları için ~ 322.47 milyar devirin gerçek performansı Skylake'deki hızlı 105B sürümü için iç döngünün her tekrarlaması için 2.824 devire ulaşmaktadır. (Aşağıdaki ocperf.py
çıktıya bakınız). Statik analizden tahmin ettiğimden daha yavaştır, fakat dış döngünün ve herhangi bir yığın-senkronizasyon ek yükünün yükünü görmezden geliyordum.
İçin sayaçlar Perf branches
ve branch-misses
gösteri olduğunu iç döngü mispredicts dış döngü başına (çekildiği değil son yineleme, üzerine) bir kez. Bu, fazladan zamanın bir kısmını da hesaba katar.
I kullanılarak, en iç döngü kritik yol için 3 döngü gecikme var yaparak kod büyüklüğü kurtarabilir mov esi,eax
/ sub eax,ebp
/ cmovc eax, esi
/cmc
(2 + 2 + 3 + 1 = 8B) yerine lea esi, [eax - 1000000000]
/ cmp ebp,eax
/ cmovc
(6 + 2 + 3 = 11 B ). cmov
/ stosd
Kritik yol kapalıdır. (Arasında artış-edi UOP stosd
Bu den ebp init talimatı değiştirerek bir 1B kaydetmek için kullanılan bir kısa bağımlılık zincir dışında, her yineleme çatal böylece., Mağazadan ayrı çalışabilir) lea ebp, [ecx-1]
için mov ebp,eax
, ama yanlış sahip olduğu keşfedilmiştirebp
sonucu değiştirmedi. Bu, bir uzuvun bir taşıyıcıyı sarmak ve üretmek yerine tam olarak == 1000000000 olmasına izin verir, ancak bu hata Fib () büyüdüğümüzden daha yavaş yayılır, bu nedenle nihai sonucun baştaki 1k hanelerini değiştirmez. Ayrıca, sadece eklediğimizde bu hatanın kendi kendini düzeltebileceğini düşünüyorum, çünkü bir taşma taşımayacak bir uzuvda yer var. 1G + 1G bile 32 bitlik bir tamsayıdan taşmaz, bu nedenle sonunda yukarı doğru süzülür veya kesilir.
3c gecikme süresi 1 ekstra uop'tur, bu nedenle ön uç Skylake'de 2.75c döngü başına bir tane yayınlayabilir, arka uç tarafından çalıştırılandan yalnızca biraz daha hızlı olabilir. (Haswell'de hala kullandığından adc
ve toplamda 13 ay olacak ve cmov
iter başına 3.25c'de ön taraftaki tıkanıklığı olacak).
Uygulamada Skylake'de (ekstremite başına 3.34 devir) 3 / 2.5 = 1.2 yerine 1.18'lik bir yavaşlama faktörü çalıştırıyor, ön uç darboğazı ile gecikme darboğazını değiştirmeden önce iç döngüde yığın senkronizasyonu olmadan bakmak için öngördüm UOPs. Yığın senkronizasyonu sadece hızlı sürüme zarar verdiğinden (gecikme yerine ön uçtan tıkanmış), açıklamak için fazla bir şey gerekmez. örneğin 3 / 2.54 = 1.18.
Diğer bir faktör ise, 3c gecikme versiyonunun kritik yol hala yürütülürken iç halkayı terk etme konusundaki yanlış tahminin tespit edilebildiğidir (çünkü ön uç arka uçtan öne geçebilir, böylece sıra dışı yürütme döngü çalışmasına izin verir. ters)), bu nedenle etkili yanlış tahmin ceza düşüktür. Bu ön uç çevrimlerini kaybetmek, arka uçların yetişmesini sağlar.
Öyle olmasaydı cmc
, carrycout -> edx ve esp ofsetlerinin dalsız kullanımı yerine dış döngüde bir dal kullanarak 3c sürümünü hızlandırabiliriz . Veri bağımlılığı yerine kontrol bağımlılığı için Branş Tahmini + spekülatif yürütme adc
, önceki iç döngüden gelenler hala uçuştayken bir sonraki yinelemenin döngüyü çalıştırmaya başlamasını sağlayabilir. Dalsız versiyonda, iç döngüdeki yük adresleri adc
, son ekstremitenin sonundan itibaren CF'ye veri bağımlılığına sahiptir .
2c gecikmeli iç döngü versiyonu ön uçta darboğazlar oluşturur, böylece arka uç hemen hemen tutar. Dış döngü kodu yüksek gecikme süresi içeriyorsa, ön uç iç döngünün bir sonraki yinelemesinden uops çıkarmaya devam edebilir. (Ancak bu durumda dış döngü malzemeleri bol miktarda ILP'ye sahiptir ve yüksek gecikme süresi yoktur, bu nedenle arka uç, sıra dışı zamanlayıcıdaki uops ile çiğnemeye başladığında başaracak kadar şey yapmaz. girişleri hazır hale gelir).
### Output from a profiled run
$ asm-link -m32 fibonacci-1G.asm && (size fibonacci-1G; echo disas fibonacci-1G) && ocperf.py stat -etask-clock,context-switches:u,cpu-migrations:u,page-faults:u,cycles,instructions,uops_issued.any,uops_executed.thread,uops_executed.stall_cycles -r4 ./fibonacci-1G
+ yasm -felf32 -Worphan-labels -gdwarf2 fibonacci-1G.asm
+ ld -melf_i386 -o fibonacci-1G fibonacci-1G.o
text data bss dec hex filename
106 0 0 106 6a fibonacci-1G
disas fibonacci-1G
perf stat -etask-clock,context-switches:u,cpu-migrations:u,page-faults:u,cycles,instructions,cpu/event=0xe,umask=0x1,name=uops_issued_any/,cpu/event=0xb1,umask=0x1,name=uops_executed_thread/,cpu/event=0xb1,umask=0x1,inv=1,cmask=1,name=uops_executed_stall_cycles/ -r4 ./fibonacci-1G
79523178745546834678293851961971481892555421852343989134530399373432466861825193700509996261365567793324820357232224512262917144562756482594995306121113012554998796395160534597890187005674399468448430345998024199240437534019501148301072342650378414269803983873607842842319964573407827842007677609077777031831857446565362535115028517159633510239906992325954713226703655064824359665868860486271597169163514487885274274355081139091679639073803982428480339801102763705442642850327443647811984518254621305295296333398134831057713701281118511282471363114142083189838025269079177870948022177508596851163638833748474280367371478820799566888075091583722494514375193201625820020005307983098872612570282019075093705542329311070849768547158335856239104506794491200115647629256491445095319046849844170025120865040207790125013561778741996050855583171909053951344689194433130268248133632341904943755992625530254665288381226394336004838495350706477119867692795685487968552076848977417717843758594964253843558791057997424878788358402439890396,�X\�;3�I;ro~.�'��R!q��%��X'B �� 8w��▒Ǫ�
... repeated 3 more times, for the 3 more runs we're averaging over
Note the trailing garbage after the trailing digits.
Performance counter stats for './fibonacci-1G' (4 runs):
73438.538349 task-clock:u (msec) # 1.000 CPUs utilized ( +- 0.05% )
0 context-switches:u # 0.000 K/sec
0 cpu-migrations:u # 0.000 K/sec
2 page-faults:u # 0.000 K/sec ( +- 11.55% )
322,467,902,120 cycles:u # 4.391 GHz ( +- 0.05% )
924,000,029,608 instructions:u # 2.87 insn per cycle ( +- 0.00% )
1,191,553,612,474 uops_issued_any:u # 16225.181 M/sec ( +- 0.00% )
1,173,953,974,712 uops_executed_thread:u # 15985.530 M/sec ( +- 0.00% )
6,011,337,533 uops_executed_stall_cycles:u # 81.855 M/sec ( +- 1.27% )
73.436831004 seconds time elapsed ( +- 0.05% )
( +- x %)
bu sayı için 4 turdaki standart sapmadır. Bu kadar çok sayıda talimat vermesi ilginç. Bu 924 milyar tesadüf değil . Dış döngünün toplam 924 komut çalıştırdığını tahmin ediyorum.
uops_issued
erimiş bir etki alanı sayısıdır (ön uç sorun bant genişliği için geçerlidir), uops_executed
erimiş bir etki alanı sayısıdır (yürütme bağlantı noktalarına gönderilen uops sayısı). Mikro-füzyon, 2 kaynaşmamış etki alanı uops'unu bir kaynaşık etki alanı uop'una paketler, ancak mov-eliminasyonu , bazı kaynaşmış etki alanı uçlarının herhangi bir yürütme portuna ihtiyaç duymadığı anlamına gelir. Uops ve kaynaşmış ve kaynaşmamış alan adlarının sayılması hakkında daha fazla bilgi için bağlantılı soruya bakınız. (Ayrıca Agner Fog'un kullanım tablolarına ve uç kılavuzuna ve SO x86 etiketi wiki'deki diğer faydalı bağlantılara bakın ).
Farklı şeyler ölçen bir başka çalışmada: L1D önbellek özlüyor, aynı iki 456B arabellek okumak / yazmak için beklendiği gibi, tamamen önemsiz. İç ilmek dalı, dış ilmek başına bir kez yanlış girer (ilmek bırakmak için alınmadığında). (Bilgisayar tamamen boşta olmadığı için toplam süre daha yüksektir. Muhtemelen diğer mantıksal çekirdek bir süre daha aktifti ve kesintilerde daha fazla zaman harcandı (kullanıcı alanı tarafından ölçülen frekans 4.400GHz'in altında olduğundan). Ya da çoklu çekirdekler çoğu zaman etkindi, maksimum turboyu düşürdü. cpu_clk_unhalted.one_thread_active
HT yarışmasının bir sorun olup olmadığını görmedim.)
### Another run of the same 105/106B "main" version to check other perf counters
74510.119941 task-clock:u (msec) # 1.000 CPUs utilized
0 context-switches:u # 0.000 K/sec
0 cpu-migrations:u # 0.000 K/sec
2 page-faults:u # 0.000 K/sec
324,455,912,026 cycles:u # 4.355 GHz
924,000,036,632 instructions:u # 2.85 insn per cycle
228,005,015,542 L1-dcache-loads:u # 3069.535 M/sec
277,081 L1-dcache-load-misses:u # 0.00% of all L1-dcache hits
0 ld_blocks_partial_address_alias:u # 0.000 K/sec
115,000,030,234 branches:u # 1543.415 M/sec
1,000,017,804 branch-misses:u # 0.87% of all branches
Kodum Ryzen'de daha az sayıda döngüde çalışabilir, bu da döngü başına 5 uops verebilir (veya bazıları Ryzen'deki AVX 256b gibi 2-uut komut olduğunda 6). Ön stosd
uçunun ne yapacağından emin değilim , ki bu Ryzen'de 3 uops (Intel ile aynı). İç döngüdeki diğer talimatların Skylake ile aynı gecikme olduğunu düşünüyorum. (Buna dahil adc eax, [edi+edx]
, Skylake'e göre bir avantaj).
Sayıları bayt başına 1 ondalık basamak olarak kaydedersem, bu muhtemelen daha küçük olabilir, ancak belki 9 kat daha yavaş olabilir . Birlikte gerçekleştirme cmp
ve ayarlama yapma cmov
aynı işe yarar, ancak işi 1/9 yapar. Bayt başına 2 ondalık basamak (temel-100, yavaşDAA
4 bit BCD değil ) de çalışır ve div r8
/ add ax, 0x3030
99 basma sırasındaki 0-99 baytı iki ASCII basamağa dönüştürür. Ancak bayt başına 1 basamak olması gerekmez div
, sadece döngü ve 0x30 eklenir. Baytları yazdırma sırasına göre saklarsam, bu 2. döngüyü gerçekten basitleştirir.
64 bit tam sayı başına 18 veya 19 ondalık basamak kullanmak (64 bit modunda), yaklaşık iki kat daha hızlı çalışmasını sağlar, ancak tüm REX önekleri ve 64 bit sabitleri için önemli kod boyutu tutar. 64 bit modunda 32 bit uzuvlar pop eax
yerine kullanılmasını önler lodsd
. Hala kullanarak REX önekleri önlemek olabilir esp
(kullanımını takas olmayan bir işaretçi sıfırdan kaydı olarak esi
ve esp
) yerine kullanarak r8d
bir 8 kaydı olarak.
Çağrılabilir bir işlev sürümü oluşturuyorsanız, 64-bit'e dönüştürme ve kullanma r8d
, kaydetme / geri yükleme işleminden daha ucuz olabilir rsp
. 64 bit, bir bayt dec r32
kodlamayı da kullanamaz (çünkü bir REX önekidir). Ama çoğunlukla dec bl
2 bayt olanı kullandım . (Çünkü üst baytlarda bir sabite sahibim ebx
ve sadece sabitin düşük baytı olduğu için çalışan iç döngülerin dışında kullanıyorum 0x00
.)
Yüksek performanslı versiyon
Maksimum performans için (kod-golf değil), iç döngüyü açmak istediğinizde, en fazla 22 yineleme çalıştıracaksınız, bu da şube belirleyicilerinin iyi yapması için yeterince kısa / alınmamış bir kalıptır. Deneylerimde, mov cl, 22
bir .inner: dec cl/jnz .inner
döngü çok az yanlış anlamadan önce (% 0.05 gibi, iç döngünün tam çalışması başına bir taneden çok daha az), ancak mov cl,23
iç döngü başına 0.35 ila 0.6 kez yanlış tahmin eder. 46
özellikle kötüdür, iç döngü başına ~ 1.28 kat yanlış tahmin eder (100M dış döngü yineleme için 128M kat). 114
Fibonacci döngüsünün bir parçası olarak bulduğum aynı iç döngüde tam olarak bir kez yanlış anlaşıldı.
Merak ettim ve denedim, iç döngüyü 6 ile açın %rep 6
(çünkü 114 eşit şekilde bölüdür). Bu çoğunlukla şube özlerini ortadan kaldırdı. edx
Negatif hale getirdim ve mov
mağazalar için dengeleme olarak kullandım , böylece adc eax,[edi]
mikro kaynaşık kalabilirdi. (Ve böylece engelleyebildim stosd
). Ben çekti lea
güncellemek için edi
dışarı %rep
, bloğun sadece 6 mağaza başına bir işaretçi güncelleme eriyor.
Dış döngüdeki tüm kısmi sicillerden de kurtuldum, bunun önemli olduğunu sanmıyorum. Dış döngünün sonunda CF'nin nihai ADC'ye bağlı olmayan bir şekilde hafifçe olmasına yardımcı olmuş olabilir, bu yüzden bazı iç döngü uçları başlayabilir. Dış döngü kodu muhtemelen biraz daha fazla optimize edilebildi, çünkü neg edx
yaptığım son şeydi, xchg
sadece 2 mov
komutla değiştirdikten sonra (zaten 1'im vardı) ve 8-bit bırakma ile birlikte zincirleri yeniden düzenleme bir şeyler kaydet.
Bu sadece Fibonacci döngüsünün NASM kaynağıdır. Orijinal sürümün bu bölümü için bir değiştirme işlemidir.
;;;; Main loop, optimized for performance, not code-size
%assign unrollfac 6
mov bl, limbcount/unrollfac ; and at the end of the outer loop
align 32
.fibonacci:
limbcount equ 114 ; 112 = 1006 decimal digits / 9 digits per limb. Not enough for 1000 correct digits, but 114 is.
; 113 would be enough, but we depend on limbcount being even to avoid a sub
; align 8
.digits_add:
%assign i 0
%rep unrollfac
;lodsd ; Skylake: 2 uops. Or pop rax with rsp instead of rsi
; mov eax, [esp]
; lea esp, [esp+4] ; adjust ESP without affecting CF. Alternative, load relative to edi and negate an offset? Or add esp,4 after adc before cmp
pop eax
adc eax, [edi+i*4] ; read from a potentially-offset location (but still store to the front)
;; jz .out ;; Nope, a zero digit in the result doesn't mean the end! (Although it might in base 10**9 for this problem)
lea esi, [eax - 1000000000]
cmp ebp, eax ; sets CF when (base-1) < eax. i.e. when eax>=base
cmovc eax, esi ; eax %= base, keeping it in the [0..base) range
%if 0
stosd
%else
mov [edi+i*4+edx], eax
%endif
%assign i i+1
%endrep
lea edi, [edi+4*unrollfac]
dec bl ; preserves CF. The resulting partial-flag merge on ADC would be slow on pre-SnB CPUs
jnz .digits_add
; bl=0, ebx=-1024
; esi has its high bit set opposite to CF
.end_innerloop:
;; after a non-zero carry-out (CF=1): right-shift both buffers by 1 limb, over the course of the next two iterations
;; next iteration with r8 = 1 and rsi+=4: read offset from both, write normal. ends with CF=0
;; following iter with r8 = 1 and rsi+=0: read offset from dest, write normal. ends with CF=0
;; following iter with r8 = 0 and rsi+=0: i.e. back to normal, until next carry-out (possible a few iters later)
;; rdi = bufX + 4*limbcount
;; rsi = bufY + 4*limbcount + 4*carry_last_time
; setc [rdi]
; mov dl, dh ; edx=0. 2c latency on SKL, but DH has been ready for a long time
; adc edx,edx ; edx = CF. 1B shorter than setc dl, but requires edx=0 to start
setc al
movzx edx, al
mov [edi], edx ; store the carry-out into an extra limb beyond limbcount
shl edx, 2
;; Branching to handle the truncation would break the data-dependency (of pointers) on carry-out from this iteration
;; and let the next iteration start, but we bottleneck on the front-end (9 uops)
;; not the loop-carried dependency of the inner loop (2 cycles for adc->cmp -> flag input of adc next iter)
;; Since the pattern isn't perfectly regular, branch mispredicts would hurt us
; keep -1024 in ebx. Using bl for the limb counter leaves bl zero here, so it's back to -1024 (or -2048 or whatever)
mov eax, esp
and esp, 4 ; only works if limbcount is even, otherwise we'd need to subtract limbcount first.
and edi, ebx ; -1024 ; revert to start of buffer, regardless of offset
add edi, edx ; read offset in next iter's src
;; maybe or edi,edx / and edi, 4 | -1024? Still 2 uops for the same work
;; setc dil?
;; after adjusting src, so this only affects read-offset in the dst, not src.
or edx, esp ; also set r8d if we had a source offset last time, to handle the 2nd buffer
mov esp, edi
; xchg edi, esp ; Fibonacci: dst and src swap
and eax, ebx ; -1024
;; mov edi, eax
;; add edi, edx
lea edi, [eax+edx]
neg edx ; negated read-write offset used with store instead of load, so adc can micro-fuse
mov bl, limbcount/unrollfac
;; Last instruction must leave CF clear for next iter
; loop .fibonacci ; Maybe 0.01% slower than dec/jnz overall
; dec ecx
sub ecx, 1 ; clear any flag dependencies. No faster than dec, at least when CF doesn't depend on edx
jnz .fibonacci
Verim:
Performance counter stats for './fibonacci-1G-performance' (3 runs):
62280.632258 task-clock (msec) # 1.000 CPUs utilized ( +- 0.07% )
0 context-switches:u # 0.000 K/sec
0 cpu-migrations:u # 0.000 K/sec
3 page-faults:u # 0.000 K/sec ( +- 12.50% )
273,146,159,432 cycles # 4.386 GHz ( +- 0.07% )
757,088,570,818 instructions # 2.77 insn per cycle ( +- 0.00% )
740,135,435,806 uops_issued_any # 11883.878 M/sec ( +- 0.00% )
966,140,990,513 uops_executed_thread # 15512.704 M/sec ( +- 0.00% )
75,953,944,528 resource_stalls_any # 1219.544 M/sec ( +- 0.23% )
741,572,966 idq_uops_not_delivered_core # 11.907 M/sec ( +- 54.22% )
62.279833889 seconds time elapsed ( +- 0.07% )
Bu aynı Fib (1G) için, aynı çıktıyı 73 saniye yerine 62,3 saniyede üretiyor. (273.146G döngü, 322.467G vs.. Her şey L1 önbelleğinde yer aldığından, çekirdek saat döngüleri gerçekten göz atmamız gereken tek şeydir.)
uops_issued
Sayının çok altında , çok daha düşük toplam sayıma dikkat edin uops_executed
. Bu, çoğunun mikro kaynaşmış olduğu anlamına gelir: kaynaşık alanda 1 uop (sorun / ROB), ancak kaynaşmamış alanda 2 uop (zamanlayıcı / yürütme birimleri)). Bu sayı, sorun / yeniden adlandırma aşamasında da ortadan kaldırıldı ( yayınlanması gereken ancak bir yürütme birimine ihtiyaç duymayan, sıfırlama mov
veya xor
sıfırlama gibi ). Ortadan kaldırılmış uops, diğer şekilde sayım dengesizliği olur.
branch-misses
1G'den ~ 400k'ya kadar düştü, bu yüzden unrolling çalıştı. resource_stalls.any
şimdi anlamlıdır, yani ön uç artık darboğaz değildir: bunun yerine arka uç arkaya gidiyor ve ön ucunu sınırlıyor. idq_uops_not_delivered.core
Sadece ön uç UOPs teslim etmedi döngüleri sayar, ancak arka uç değildi durdu. Bu, hoş ve alçak, birkaç ön uç tıkanıklığı olduğunu gösterir.
Eğlenceli gerçek: python versiyonu, zamanının yarısından fazlasını ilave etmek yerine 10'a bölünerek geçiriyor. (Değiştirme a/=10
ile a>>=64
daha 2 faktör daha tarafından hızlarda o kadar, ama ikili bir kesilmeye çünkü sonucu değiştirir! = Ondalık kesilmesi.)
Asm versiyonum elbette özellikle bu problem boyutu için optimize edilmiştir, döngü yineleme sayıları sabit kodlanmıştır. İsteğe bağlı hassas bir numarayı değiştirmek bile kopyalayacaktır, ancak sürümüm sonraki iki yinelemenin bile atlaması için bir ofsetten okuyabilir.
Python sürümünü (Arch Linux'ta 64-bit python2.7) profilledim :
ocperf.py stat -etask-clock,context-switches:u,cpu-migrations:u,page-faults:u,cycles,instructions,uops_issued.any,uops_executed.thread,arith.divider_active,branches,branch-misses,L1-dcache-loads,L1-dcache-load-misses python2.7 ./fibonacci-1G.anders-brute-force.py
795231787455468346782938519619714818925554218523439891345303993734324668618251937005099962613655677933248203572322245122629171445627564825949953061211130125549987963951605345978901870056743994684484303459980241992404375340195011483010723426503784142698039838736078428423199645734078278420076776090777770318318574465653625351150285171596335102399069923259547132267036550648243596658688604862715971691635144878852742743550811390916796390738039824284803398011027637054426428503274436478119845182546213052952963333981348310577137012811185112824713631141420831898380252690791778709480221775085968511636388337484742803673714788207995668880750915837224945143751932016258200200053079830988726125702820190750937055423293110708497685471583358562391045067944912001156476292564914450953190468498441700251208650402077901250135617787419960508555831719090539513446891944331302682481336323419049437559926255302546652883812263943360048384953507064771198676927956854879685520768489774177178437585949642538435587910579974100118580
Performance counter stats for 'python2.7 ./fibonacci-1G.anders-brute-force.py':
755380.697069 task-clock:u (msec) # 1.000 CPUs utilized
0 context-switches:u # 0.000 K/sec
0 cpu-migrations:u # 0.000 K/sec
793 page-faults:u # 0.001 K/sec
3,314,554,673,632 cycles:u # 4.388 GHz (55.56%)
4,850,161,993,949 instructions:u # 1.46 insn per cycle (66.67%)
6,741,894,323,711 uops_issued_any:u # 8925.161 M/sec (66.67%)
7,052,005,073,018 uops_executed_thread:u # 9335.697 M/sec (66.67%)
425,094,740,110 arith_divider_active:u # 562.756 M/sec (66.67%)
807,102,521,665 branches:u # 1068.471 M/sec (66.67%)
4,460,765,466 branch-misses:u # 0.55% of all branches (44.44%)
1,317,454,116,902 L1-dcache-loads:u # 1744.093 M/sec (44.44%)
36,822,513 L1-dcache-load-misses:u # 0.00% of all L1-dcache hits (44.44%)
755.355560032 seconds time elapsed
(Parens) içindeki sayılar, perf sayacının örneklendiği zamanın ne kadarıdır. HW'nin desteklediğinden daha fazla sayıcıya bakarken, perf farklı sayıcılar ve ekstrapolatlar arasında döner. Aynı işin uzun sürmesi için bu tamamen iyi.
Eğer perf
sysctl ayarını yaptıktan sonra koştuysam kernel.perf_event_paranoid = 0
(veya perf
root olarak çalıştırıyorsam ), bunu ölçecekti 4.400GHz
. cycles:u
kesintilerde (veya sistem çağrılarında) harcanan zamanı saymaz, yalnızca kullanıcı alanı döngülerini sayar. Masaüstüm neredeyse tamamen boştaydı, ancak bu normal.
Your program must be fast enough for you to run it and verify its correctness.
Peki ya hafıza?