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?
cout.operator<<()
. El compilador no sabe que esta función de recuadro negro no obtiene una referencia std::vector
de un global.
println
o operator<<
es clave.
println
es probablemente un método complejo, el compilador puede tener problemas para probar queprintln
no muta el vector.