Я обратился к коллеге, который 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);
}
}
Согласно Compiler Explorer с опциями 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
Интересно, что в Rust такая оптимизация действительно происходит. Похоже, 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);
}
}
}
В Compiler Explorer соответствующая сборка выглядит так:
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
он не изменяет вектор.