Melhor caso 8 ciclos, pior caso 12 ciclos
Como não está claro na pergunta, estou baseando isso nas latências da Ivy Bridge.
A abordagem aqui é usar a bsr
instrução (bit scan reverse) como um log2 de pobre (). O resultado é usado como um índice em uma tabela de salto que contém entradas para os bits de 0 a 42. Estou assumindo que, dado que a operação em dados de 64 bits é implicitamente necessária, o uso da bsr
instrução é OK.
Na melhor das hipóteses, a entrada saltável pode mapear o bsr
resultado diretamente para a magnitude. por exemplo, para entradas no intervalo 32-63, o bsr
resultado será 5, que é mapeado para uma magnitude de 1. Nesse caso, o caminho da instrução é:
Instruction Latency
bsrq 3
jmp 2
movl 1
jmp 2
total 8
Na pior das hipóteses, o bsr
resultado será mapeado para duas magnitudes possíveis; portanto, a entrada saltável faz uma adicional cmp
para verificar se a entrada é> 10 n . Por exemplo, para entradas na faixa de 64 a 127, o bsr
resultado será 6. A entrada jumptable correspondente verifica se a entrada é> 100 e define a magnitude da saída de acordo.
Além do caminho do pior caso, temos uma instrução mov adicional para carregar um valor imediato de 64 bits para uso no cmp
, portanto, o caminho do pior caso é:
Instruction Latency
bsrq 3
jmp 2
movabsq 1
cmpq 1
ja 2
movl 1
jmp 2
total 12
Aqui está o código:
/* 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 */
Isso foi gerado principalmente a partir da saída do montador gcc para o código C de prova de conceito que eu escrevi . Observe que o código C usa um goto computável para implementar a tabela de salto. Ele também usa o __builtin_clzll()
gcc builtin, que é compilado com a bsr
instrução (mais uma xor
).
Eu considerei várias soluções antes de chegar a esta:
FYL2X
para calcular o log natural e, em seguida, FMUL
pela constante necessária. Presumivelmente, isso venceria se fosse um concurso [tag: instruction: golf]. Mas FYL2X
tem como latência de 90-106 para a ponte Ivy.
Pesquisa binária codificada. Isso pode ser realmente competitivo - deixarei para outra pessoa implementar :).
Tabela de pesquisa completa de resultados. Eu tenho certeza que isso é teoricamente mais rápido, mas exigiria uma tabela de pesquisa de 1 TB - ainda não prática - talvez em alguns anos se a Lei de Moore continuar em vigor.