Mejor caso 8 ciclos, peor caso 12 ciclos
Como no está claro en la pregunta, estoy basando esto en las latencias de Ivy Bridge.
El enfoque aquí es utilizar la bsr
instrucción (escaneo de bits inverso) como log2 () de un hombre pobre. El resultado se usa como índice en una tabla de salto que contiene entradas para los bits 0 a 42. Supongo que dado que la operación en datos de 64 bits es implícitamente requerida, entonces el uso de la bsr
instrucción está bien.
En el mejor de los casos, la entrada jumptable puede asignar el bsr
resultado directamente a la magnitud. Por ejemplo, para entradas en el rango 32-63, el bsr
resultado será 5, que se asigna a una magnitud de 1. En este caso, la ruta de instrucciones es:
Instruction Latency
bsrq 3
jmp 2
movl 1
jmp 2
total 8
En el peor de los casos, el bsr
resultado se correlacionará con dos posibles magnitudes, por lo que la entrada de la tabla de salto hace uno adicional cmp
para verificar si la entrada es> 10 n . Por ejemplo, para las entradas en el rango 64-127, el bsr
resultado será 6. La entrada correspondiente de la tabla de salto verifica si la entrada> 100 y establece la magnitud de salida en consecuencia.
Además de la ruta del peor de los casos, tenemos una instrucción mov adicional para cargar un valor inmediato de 64 bits para usar en cmp
, por lo que la ruta de la instrucción del peor caso es:
Instruction Latency
bsrq 3
jmp 2
movabsq 1
cmpq 1
ja 2
movl 1
jmp 2
total 12
Aquí está el 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 */
Esto se generó principalmente a partir de la salida del ensamblador gcc para el código C de prueba de concepto que escribí . Tenga en cuenta que el código C usa un goto computable para implementar la tabla de salto. También utiliza el __builtin_clzll()
gcc incorporado, que compila las bsr
instrucciones (más un xor
).
Consideré varias soluciones antes de llegar a esta:
FYL2X
para calcular el registro natural, luego FMUL
por la constante necesaria. Esto presumiblemente ganaría esto si fuera un concurso [tag: instrucción: golf]. Pero FYL2X
tiene una latencia de 90-106 para el puente Ivy.
Búsqueda binaria codificada. En realidad, esto puede ser competitivo: dejaré que alguien más lo implemente :).
Tabla de búsqueda completa de resultados. Estoy seguro de que esto es teóricamente más rápido, pero requeriría una tabla de búsqueda de 1TB, aún no práctica, tal vez en unos pocos años si la Ley de Moore continúa vigente.