Afirmei a um colega de trabalho que if (i < input.size() - 1) print(0);
seria otimizado nesse loop para que input.size()
não seja lido em todas as iterações, mas acontece que esse não é o 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 acordo com o Compiler Explorer com as opções gcc -O3 -fno-exceptions
, na verdade, estamos lendo input.size()
cada iteração e usando lea
para executar uma subtração!
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, no Rust, essa otimização ocorre. Parece que i
é substituído por uma variável j
que é decrementada a cada iteração, e o teste i < input.size() - 1
é substituído por algo parecido 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);
}
}
}
No Explorer do compilador, o assembly relevante se parece com isso:
cmpq %r12, %rbx
jae .LBB0_4
Eu verifiquei e tenho certeza que r12
é xs.len() - 1
e rbx
é o contador. Anteriormente, há um add
for rbx
e um mov
fora do loop r12
.
Por que é isso? Parece que, se o GCC for capaz de alinhar o size()
e, operator[]
como fez, deve saber que size()
isso não muda. Mas talvez o otimizador do GCC julgue que não vale a pena puxá-lo para uma variável? Ou talvez haja algum outro efeito colateral possível que tornaria isso inseguro - alguém sabe?
cout.operator<<()
. O compilador não sabe que essa função de caixa preta não obtém uma referência ao std::vector
global.
println
ou operator<<
é fundamental.
println
é provavelmente um método complexo, o compilador pode ter problemas para provar queprintln
não altera o vetor.