Я заявляв колезі, який if (i < input.size() - 1) print(0);
би оптимізувався в цьому циклі, щоб input.size()
його не читали в кожній ітерації, але виявляється, що це не так!
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);
}
}
Відповідно до Провідника компілятора з параметрами gcc, -O3 -fno-exceptions
ми насправді читаємо input.size()
кожну ітерацію та використовуємо lea
для виконання віднімання!
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
Цікаво, що в Русті така оптимізація все ж відбувається. Схоже, i
його замінюють змінною, j
яка зменшується кожною ітерацією, а тест i < input.size() - 1
замінюється чимось подібним 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);
}
}
}
У Провіднику компілятора відповідна збірка виглядає так:
cmpq %r12, %rbx
jae .LBB0_4
Я перевірив, і я впевнений, що r12
це xs.len() - 1
і rbx
є лічильник. Раніше існують add
для rbx
і mov
зовні петлі вr12
.
Чому це? Схоже, якщо GCC здатний вписати size()
і operator[]
як це зробив, він повинен мати можливість знати, що size()
не змінюється. Але, можливо, оптимізатор GCC вважає, що не варто його витягувати в змінну? А може, є якийсь інший можливий побічний ефект, який би зробив це небезпечним - хтось знає?
cout.operator<<()
. Компілятор не знає, що ця функція чорного поля не отримує посилання на std::vector
глобальну.
println
або operator<<
є ключовим.
println
, ймовірно, складний метод, компілятор може мати проблеми з доведенням, щоprintln
не мутує вектор.