Рассмотрим следующий код:
int g(std::vector<int>&, size_t);
int f(std::vector<int>& v) {
int res = 0;
for (size_t i = 0; i < v.size(); i++)
res += g(v, i);
return res;
}
Компилятор не может оптимизировать оценку v.size()
в цикле, поскольку он не может доказать, что размер не изменится внутри g
. Сборка, сгенерированная с помощью GCC 9.2, -O3
и x64:
.L3:
mov rsi, rbx
mov rdi, rbp
add rbx, 1
call g(std::vector<int, std::allocator<int> >&, unsigned long)
add r12d, eax
mov rax, QWORD PTR [rbp+8] // load a poniter
sub rax, QWORD PTR [rbp+0] // subtract another pointetr
sar rax, 2 // result * sizeof(int) => size()
cmp rbx, rax
jb .L3
Если мы знаем, что g
не изменяет v.size()
, мы можем переписать цикл следующим образом:
for (size_t i = 0, e = v.size(); i < e; i++)
res += g(v);
Создает более простую (и, следовательно, более быструю) сборку без этих инструкций mov
, sub
и sar
. Значение size()
просто хранится в регистре.
Я ожидаю, что тот же эффект может быть достигнут путем создания вектора const
(я знаю, что это меняет семантику программы, поскольку g
теперь не может изменять элементы вектора, но это не должно относиться к вопросу):
int g(const std::vector<int>&, size_t);
int f(const std::vector<int>& v) {
int res = 0;
for (size_t i = 0; i < v.size(); i++)
res += g(v, i);
return res;
}
Теперь компилятор должен знать, что эти указатели, загруженные в каждой итерации цикла, не могут измениться и, следовательно, результат
mov rax, QWORD PTR [rbp+8]
sub rax, QWORD PTR [rbp+0]
sar rax, 2
всегда одинаково. Несмотря на это, эти инструкции присутствуют в сгенерированной сборке;Демо-версия здесь .
Я также попробовал Intel 19, Clang 9 и MSVC 19 с теми же результатами. Поскольку все основные компиляторы ведут себя одинаково, Интересно, есть ли какое-то правило, запрещающее этот вид оптимизации, т. Е. Выведение оценки size()
для постоянного вектора из цикла .