Performance issue for vector::size() in a loop in C++

前端 未结 10 777
离开以前
离开以前 2020-11-29 04:37

In the following code:

std::vector var;
for (int i = 0; i < var.size(); i++);

Is the

相关标签:
10条回答
  • 2020-11-29 05:06

    As other have said

    • the semantics must be as if it were called each time
    • it is probably inlined, and is probably a simple function

    on top of which

    • a smart enough optimizer may be able to deduce that it is a loop invariant with no side effects and elide it entirely (this is easier if the code is inlined, but may be possible even if it is not if the compiler does global optimization)
    0 讨论(0)
  • 2020-11-29 05:06

    I think that if the compiler can conclusively deduce that the variable var is not modified inside the "loop body"

    for(int i=0; i< var.size();i++) { 
        // loop body
    }
    

    then the above may be transposed to something equivalent of

    const size_t var_size = var.size();
    for( int i = 0; i < var_size; i++ ) { 
        // loop body
    }
    

    However, I am not absolutely sure, so comments are welcome :)

    Also,

    • In most situations, the size() member function is inlined, so the issue does not warrant worrying

    • The concern is perhaps equally applicable to the end() which is always used for iterator based looping, i.e. it != container.end()

    • Please consider using size_t or vector<int>::size_type for the type of i [See Steve Jessop's comment below.]

    0 讨论(0)
  • 2020-11-29 05:08

    The problem with your question is that it does not make any sense. A C++ compiler translates some source code into a binary program. The requirement is that the resulting program must preserve observable effects of the code according to the rules of the C++ Standard. This code:

    for (int i = 0; i < var.size(); i++); 
    

    simply does not have any observable effect. Moreover, it does not interact with the surrounding code any way, and the compiler may optimize it completely away; that is to generate no corresponding assembly.

    To make your question meaningful, you need to specify what happens inside the loop. The problem with

    for (int i = 0; i < var.size(); i++) { ... }
    

    is that the answer very much depends on what ... actually is. I believe @MatteoItalia provided a very nice answer, just would add a description of some experiments I made. Consider the following code:

    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;
    }
    

    First, even if calling var.size() will almost 100% sure be inlined with enabled optimizations, and this inlining typically translates into a subtraction of two pointers, this still brings into the loop some overhead. If a compiler is not able to prove that the vector size is preserved (which, generally, is very difficult or even infeasible, such as in our case), then you will end up with unnecessary load and sub (and, possibly, shift) instructions. The generated assembly of the loop with GCC 9.2, -O3, and x64 was:

    .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] // loads a pointer
        sub     rax, QWORD PTR [rbp+0] // subtracts another poniter
        sar     rax, 2                 // result * sizeof(int) => size()
        cmp     rbx, rax
        jb      .L3
    

    If we rewrite the code as follows:

    int g(std::vector<int>&, size_t);
    
    int f(std::vector<int>& v) {
       int res = 0;
       for (size_t i = 0, e = v.size(); i < e; i++)
          res += g(v, i);
       return res;
    }
    

    then, the generated assembly is simpler (and, therefore, faster):

    .L3:
        mov     rsi, rbx
        mov     rdi, r13
        add     rbx, 1
        call    g(std::vector<int, std::allocator<int> >&, unsigned long)
        add     r12d, eax
        cmp     rbx, rbp
        jne     .L3
    

    The value of the vector's size is simply kept in a register (rbp).

    I even tried a different version where the vector is marked as being const:

    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;
    }
    

    Surprisingly, even when v.size() cannot change here, the generated assembly was the same as in the first case (with additional mov, sub, and sar instructions).

    Live demo is here.

    Additionally, when I changed the loop into:

    for (size_t i = 0; i < v.size(); i++)
       res += v[i];
    

    then, there was no evaluation of v.size() (subtraction of pointers) within the loop on an assembly level. GCC was able to "see" here, that the body of the loop does not alter the size any way.

    0 讨论(0)
  • 2020-11-29 05:17

    But it could be done in this way (providing that this loop intends to only read/write without actually changing the size of a vector):

    for(vector<int>::size_type i=0, size = var.size(); i < size; ++i) 
    {
    //do something
    }
    

    In the loop above you have just one call to size independently from size being inlined or not.

    0 讨论(0)
提交回复
热议问题