Dan*_*ica 4 c++ optimization loops const vector
考虑以下代码:
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,这将生成更简单(因而更快)的汇编。的值只是保存在寄存器中。subsarsize()
我希望通过制作矢量可以达到相同的效果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()恒定向量的求值移出循环。
加法const并不意味着g不能改变向量的大小。可悲的是。
如果您修改本身就是一个对象,则会得到UB const。const只要原始(即,被引用的)对象不是,则修改您具有引用的对象不是UB const。
换句话说,这是一个有效的程序:
int g(const std::vector<int>& v, size_t)
{
    const_cast<std::vector<int>&>(v).clear();
    return 0;
}
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;
}
void test()
{
    std::vector<int> v;
    f(v);
}
请注意,如果我们看不到的定义g,const即使我们不允许,也没有关系const_cast:
std::vector<int> global_v;
int g(const std::vector<int>& v, size_t)
{
    global_v.clear();
    return 0;
}
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;
}
void test()
{
    f(global_v);
}
考虑const将引用作为对您自己(和其他人)的提醒和编译器强制的保护措施,但不应该将其视为编译器的优化机会。但是,将值/对象const本身标记为改进优化的可能性很大-将实际值传递const Something给不透明的函数可以保证Something以后保留相同的内容(但请注意,这const不能通过指针或引用成员传递)。