Le reclamé a un compañero de trabajo que if (i < input.size() - 1) print(0);
se optimizaría en este ciclo para que input.size()
no se lea en cada iteración, ¡pero resulta que este no es el caso!
void print(int x) {
std::cout << x << std::endl;
}
void print_list(const std::vector<int>& input) {
int i = 0;
for (size_t i = 0; i < input.size(); i++) {
print(input[i]);
if (i < input.size() - 1) print(0);
}
}
De acuerdo con el Explorador de compiladores con opciones de gcc, -O3 -fno-exceptions
¡en realidad estamos leyendo input.size()
cada iteración y usándola lea
para realizar una resta!
movq 0(%rbp), %rdx
movq 8(%rbp), %rax
subq %rdx, %rax
sarq $2, %rax
leaq -1(%rax), %rcx
cmpq %rbx, %rcx
ja .L35
addq $1, %rbx
Curiosamente, en Rust se produce esta optimización. Parece que i
se reemplaza con una variable j
que se reduce cada iteración, y la prueba i < input.size() - 1
se reemplaza con algo así j > 0
.
fn print(x: i32) {
println!("{}", x);
}
pub fn print_list(xs: &Vec<i32>) {
for (i, x) in xs.iter().enumerate() {
print(*x);
if i < xs.len() - 1 {
print(0);
}
}
}
En el Explorador del compilador, el ensamblaje relevante se ve así:
cmpq %r12, %rbx
jae .LBB0_4
Lo comprobé y estoy bastante seguro de que r12
es xs.len() - 1
y rbx
es el contador. Anteriormente hay un add
for rbx
y un mov
fuera del bucle en r12
.
¿Por qué es esto? Parece que si GCC puede en línea size()
y, operator[]
como lo hizo, debería saber que eso size()
no cambia. ¿Pero quizás el optimizador de GCC considera que no vale la pena convertirlo en una variable? O tal vez exista algún otro efecto secundario posible que lo haga inseguro. ¿Alguien sabe?
println
es probablemente un método complejo, el compilador puede tener problemas para probar queprintln
no muta el vector.cout.operator<<()
. El compilador no sabe que esta función de recuadro negro no obtiene una referenciastd::vector
de un global.println
ooperator<<
es clave.