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-exceptions
realtà stiamo leggendo input.size()
ogni iterazione e usando lea
per 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 i
venga sostituito con una variabile j
che viene decrementata ad ogni iterazione e il test i < input.size() - 1
viene 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 r12
sia xs.len() - 1
ed rbx
è il contatore. In precedenza c'è un add
for rbx
e un mov
esterno 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::vector
da un globale.
println
o operator<<
è la chiave.
println
è probabilmente un metodo complesso, il compilatore potrebbe avere difficoltà a provare cheprintln
non muta il vettore.