¿Por qué los compiladores modernos no fusionan los accesos a memoria vecinos?
Considere el siguiente código:
bool AllZeroes(const char buf[4])
{
return buf[0] == 0 &&
buf[1] == 0 &&
buf[2] == 0 &&
buf[3] == 0;
}
Salida del ensamblador de Clang 13 con -O3
:
AllZeroes(char const*): # @AllZeroes(char const*)
cmp byte ptr [rdi], 0
je .LBB0_2
xor eax, eax
ret
.LBB0_2:
cmp byte ptr [rdi + 1], 0
je .LBB0_4
xor eax, eax
ret
.LBB0_4:
cmp byte ptr [rdi + 2], 0
je .LBB0_6
xor eax, eax
ret
.LBB0_6:
cmp byte ptr [rdi + 3], 0
sete al
ret
Cada byte se compara individualmente, pero podría haberse optimizado en una sola comparación de enteros de 32 bits:
bool AllZeroes(const char buf[4])
{
return *(int*)buf == 0;
}
Resultando en:
AllZeroes2(char const*): # @AllZeroes2(char const*)
cmp dword ptr [rdi], 0
sete al
ret
También he comprobado GCC y MSVC, y ninguno de ellos hace esta optimización. ¿Está esto prohibido por la especificación de C++?
Edición:
Cambiar el AND de circuito corto (&&
) por un AND a nivel de bits (&
) generará el código optimizado. Además, cambiar el orden en que se comparan los bytes no afecta a la generación de código: https://godbolt.org/z/Y7TcG93sP
davy.ai
La optimización de comparar los 4 bytes a la vez usando una comparación de int de 32 bits no está prohibida por la especificación de C++. Sin embargo, en este caso específico, el operador AND cortocircuitado (
&&
) está impidiendo al compilador hacer esta optimización. Cambiar el AND cortocircuitado por un AND a nivel de bits (&
) permitirá al compilador generar el código optimizado. Además, cambiar el orden en el que se comparan los bytes no afecta al código generado.