Ho affermato a un collega che if (i < input.size() - 1) print(0);sarebbe stato ottimizzato in questo ciclo in modo che input.size()non venisse letto in ogni iterazione, ma risulta che non è così!
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);
}
}
Secondo l' Explorer del compilatore con le opzioni gcc, in -O3 -fno-exceptionsrealtà stiamo leggendo input.size()ogni iterazione e usando leaper eseguire una sottrazione!
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
È interessante notare che in Rust si verifica questa ottimizzazione. Sembra che ivenga sostituito con una variabile jche viene decrementata ad ogni iterazione e il test i < input.size() - 1viene sostituito con qualcosa del genere 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);
}
}
}
Nell'Explorer del compilatore l'assembly pertinente è simile al seguente:
cmpq %r12, %rbx
jae .LBB0_4
Ho controllato e sono abbastanza sicuro che r12sia xs.len() - 1ed rbxè il contatore. In precedenza c'è un addfor rbxe un movesterno del loop in r12.
Perchè è questo? Sembra che se GCC è in grado di incorporare size()e, operator[]come ha fatto, dovrebbe essere in grado di sapere che size()non cambia. Ma forse l'ottimizzatore di GCC ritiene che non valga la pena estrarlo in una variabile? O forse c'è qualche altro effetto collaterale che potrebbe rendere questo non sicuro - qualcuno lo sa?
cout.operator<<(). Il compilatore non sa che questa funzione della scatola nera non ottiene un riferimento a std::vectorda un globale.
printlno operator<<è la chiave.
printlnè probabilmente un metodo complesso, il compilatore potrebbe avere difficoltà a provare cheprintlnnon muta il vettore.