Najlepszy przypadek 8 cykli, Najgorszy przypadek 12 cykli
Ponieważ pytanie nie jest jasne, opieram to na opóźnieniach Ivy Bridge.
Podejście polega na użyciu instrukcji bsr
(odwrotne skanowanie bitów) jako log2 biedaka (). Wynik jest używany jako indeks do tabeli skoków, która zawiera wpisy dla bitów od 0 do 42. Zakładam, że biorąc pod uwagę, że operacja na danych 64-bitowych jest niejawnie wymagana, to użycie bsr
instrukcji jest OK.
W najlepszych przypadkach dane wejściowe z możliwością zbierania mogą odwzorować bsr
wynik bezpośrednio na wielkość. np. dla danych wejściowych z zakresu 32-63 bsr
wynikiem będzie 5, który jest odwzorowany na wielkość 1. W tym przypadku ścieżka instrukcji to:
Instruction Latency
bsrq 3
jmp 2
movl 1
jmp 2
total 8
W najgorszym przypadku dane wejściowe bsr
zostaną odwzorowane na dwie możliwe wielkości, więc wpis z możliwością zbierania dodaje jeszcze jedną wartość, cmp
aby sprawdzić, czy wartość wejściowa jest> 10 n . Np. Dla danych wejściowych z zakresu 64-127 bsr
wynik będzie wynosić 6. Odpowiedni wpis , który można przesuwać, sprawdza, czy dane wejściowe> 100, i odpowiednio ustawia wielkość wyjściową.
Dodatkowo dla ścieżki najgorszego przypadku mamy dodatkową instrukcję mov, aby załadować 64-bitową wartość natychmiastową do użycia w cmp
, więc ścieżka instrukcji najgorszego przypadku to:
Instruction Latency
bsrq 3
jmp 2
movabsq 1
cmpq 1
ja 2
movl 1
jmp 2
total 12
Oto kod:
/* Input is loaded in %rdi */
bsrq %rdi, %rax
jmp *jumptable(,%rax,8)
.m0:
movl $0, %ecx
jmp .end
.m0_1:
cmpq $9, %rdi
ja .m1
movl $0, %ecx
jmp .end
.m1:
movl $1, %ecx
jmp .end
.m1_2:
cmpq $99, %rdi
ja .m2
movl $1, %ecx
jmp .end
.m2:
movl $2, %ecx
jmp .end
.m2_3:
cmpq $999, %rdi
ja .m3
movl $2, %ecx
jmp .end
.m3:
movl $3, %ecx
jmp .end
.m3_4:
cmpq $9999, %rdi
ja .m4
movl $3, %ecx
jmp .end
.m4:
movl $4, %ecx
jmp .end
.m4_5:
cmpq $99999, %rdi
ja .m5
movl $4, %ecx
jmp .end
.m5:
movl $5, %ecx
jmp .end
.m5_6:
cmpq $999999, %rdi
ja .m6
movl $5, %ecx
jmp .end
.m6:
movl $6, %ecx
jmp .end
.m6_7:
cmpq $9999999, %rdi
ja .m7
movl $6, %ecx
jmp .end
.m7:
movl $7, %ecx
jmp .end
.m7_8:
cmpq $99999999, %rdi
ja .m8
movl $7, %ecx
jmp .end
.m8:
movl $8, %ecx
jmp .end
.m8_9:
cmpq $999999999, %rdi
ja .m9
movl $8, %ecx
jmp .end
.m9:
movl $9, %ecx
jmp .end
.m9_10:
movabsq $9999999999, %rax
cmpq %rax, %rdi
ja .m10
movl $9, %ecx
jmp .end
.m10:
movl $10, %ecx
jmp .end
.m10_11:
movabsq $99999999999, %rax
cmpq %rax, %rdi
ja .m11
movl $10, %ecx
jmp .end
.m11:
movl $11, %ecx
jmp .end
.m11_12:
movabsq $999999999999, %rax
cmpq %rax, %rdi
ja .m12
movl $11, %ecx
jmp .end
.m12:
movl $12, %ecx
jmp .end
jumptable:
.quad .m0
.quad .m0
.quad .m0
.quad .m0_1
.quad .m1
.quad .m1
.quad .m1_2
.quad .m2
.quad .m2
.quad .m2_3
.quad .m3
.quad .m3
.quad .m3
.quad .m3_4
.quad .m4
.quad .m4
.quad .m4_5
.quad .m5
.quad .m5
.quad .m5_6
.quad .m6
.quad .m6
.quad .m6
.quad .m6_7
.quad .m7
.quad .m7
.quad .m7_8
.quad .m8
.quad .m8
.quad .m8_9
.quad .m9
.quad .m9
.quad .m9
.quad .m9_10
.quad .m10
.quad .m10
.quad .m10_11
.quad .m11
.quad .m11
.quad .m11_12
.quad .m12
.quad .m12
.quad .m12
.end:
/* output is given in %ecx */
Zostało to wygenerowane głównie z danych wyjściowych asemblera gcc dla kodu C w wersji proof-of-concept, który napisałem . Zauważ, że kod C używa obliczalnego goto do implementacji tabeli skoków. Używa również __builtin_clzll()
wbudowanego gcc, który kompiluje się do bsr
instrukcji (plus an xor
).
Rozważałem kilka rozwiązań, zanim doszedłem do tego:
FYL2X
obliczyć logarytm naturalny, a następnie FMUL
przez niezbędną stałą. Prawdopodobnie wygrałoby to, gdyby był to konkurs [tag: instrukcja: golf]. Ale FYL2X
ma opóźnienie 90-106 dla mostu Ivy.
Zakodowane wyszukiwanie binarne. To może faktycznie być konkurencyjne - zostawię to komuś innemu do wdrożenia :).
Kompletna tabela wyników. Jestem pewien, że jest teoretycznie najszybszy, ale wymagałby tabeli przeglądowej 1 TB - jeszcze niepraktycznej - może za kilka lat, jeśli Prawo Moore'a będzie nadal obowiązywać.