Zwróciłem się do współpracownika, if (i < input.size() - 1) print(0);
który zoptymalizowałby się w tej pętli, aby input.size()
nie był czytany przy każdej iteracji, ale okazuje się, że tak nie jest!
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);
}
}
Według Compiler Explorera z opcjami gcc -O3 -fno-exceptions
czytamy input.size()
każdą iterację i używamy lea
do odejmowania!
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
Co ciekawe, w Rust występuje taka optymalizacja. Wygląda na to, że i
zostaje zastąpiony zmienną, j
która jest zmniejszana przy każdej iteracji, a test i < input.size() - 1
jest zastępowany przez coś podobnego 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);
}
}
}
W Eksploratorze kompilatorów odpowiedni zestaw wygląda następująco:
cmpq %r12, %rbx
jae .LBB0_4
Sprawdziłem i jestem prawie pewien, że r12
jest xs.len() - 1
i rbx
jest licznikiem. Wcześniej istnieje pętla add
for rbx
i mov
zewnętrzna dla pętli r12
.
Dlaczego to? Wygląda na to, że jeśli GCC jest w stanie wprowadzić size()
i operator[]
tak jak miało to miejsce, powinien wiedzieć, że size()
to się nie zmienia. Ale może optymalizator GCC ocenia, że nie warto wyciągać go do zmiennej? A może jest jakiś inny możliwy efekt uboczny, który uczyniłby to niebezpiecznym - czy ktoś wie?
cout.operator<<()
. Kompilator nie wie, że ta funkcja czarnej skrzynki nie otrzymuje odwołania do std::vector
globalnego.
println
lub operator<<
klucz jest kluczowa.
println
Jest to również prawdopodobnie złożona metoda, kompilator może mieć problemy z udowodnieniem, żeprintln
nie mutuje wektora.