Does calling .size() in a for loop slow it down?

For example, iterating through a vector
1
2
for (int i = 0; i < peopleKnown.size(); i++)
      // do whatever 


I know I could use iterators instead, but I've already done it this way for so many vectors in my code. Is the .size() function called every time the loop repeats or is it called just one time?
Last edited on
I asked this question long time ago but I did not get the right answer to it

I think that your "peppleKnown.size()" is been calculated everytime the loop check the condition, prouf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# include <iostream>
using namespace std;

int getNumberWithNotice(void)
{
    cout << "I am called" << endl;
    return 5;
}

int main()
{
    for(int i=0;i<getNumberWithNotice();i++)
        cout << "Something" << endl;

    return 0;
}


In my codes I would do it a this way

1
2
3
int pks = peopleKnown.size();
for (int i = 0; i < pks; i++)
     // Do whatever 

this is just an opinion hope that exprets give some info
Last edited on
It's called every time the loop repeats, and iterators are indeed the better way if you don't need the index.

That said, the effect is not the same as in good old C, if you did something like:

1
2
for (size_t i=0; i < strlen(msg); ++i)
    // do something 


The above code is slow because strlen() computes the length of msg again and again.

However in your example, peopleKnown.size() returns an inner member data without actually computing the size. So it's (almost?) as fast as if you were using a variable to temporarily store the size.

Extra info: the current standard of C++, named C++11, adds range-based for loops.
The code below should compile in MinGW/GNU GCC 4.6+ and Visual C++ 2012+:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::vector<Person> vp; // assume vp is populated

for (Person p: vp)
{
    // p is akin to a local copy
}

for (Person &p: vp)
{
    // p is a reference
}

for (const Person &p: vp)
{
    // p is a const reference
}
I think that size() simply returns the value of some data member of the vector though it is called each time the loop iterates.
Ok. I went ahead and replaced ...peopleKnown.size() with a constant int for ALL the for-loops that used it. Even though peopleKnown is a data member vector of a class, the fact that it is called every single repeat of the loops made me worry nevertheless. I learned my lesson anyways. Thanks.
Ok. I went ahead and replaced ...peopleKnown.size() with a constant int for ALL the for-loops that used it.

You shouldn't have done that. If you compile your program with optimizations enabled I'm pretty sure you wouldn't be able to tell the difference anyway.

I guess that in theory by calling peopleKnown.size() repeatedly there's a function call overhead, but there are also CPU caches which "save" interesting bits for fast retrieval, for example the value returned by peopleKnown.size() so the actual function call may be omitted? (I'm not 100% sure about this.)

The point is, your intentions were good but the method was bad. I advise you to change your code back to how it was.

Keep it simple and therefore easier to read and understand by using less fewer temporary variables.
Last edited on
Or use iterators from now on for all my vectors, whenever I don't need the index, no matter how much more convenient it appears to use integer counters. That's what I meant when I said I learned my lesson.
Or use iterators from now on for all my vectors, whenever I don't need the index, no matter how much more convenient it appears to use integer counters. That's what I meant when I said I learned my lesson.


size() is undoubtedly implemented inline so it would be very fast. If you use iterators (say a variable called iter) you would typically be checking:

iter != peopleKnown.end() in your for loop. What is end() but a function call? Again, probably inlined also. Don't abandon subscripting because you think "iterators are faster". Use whichever is convenient or whichever makes the code clearer to you.
> Is the .size() function called every time the loop repeats or is it called just one time?

If the vector is const-qualified, its size() would be evaluated just once (optimized code).

If the vector is volatile-qualified, its size() would be evaluated each time through the loop.

Otherwise, it depends - if the loop contains code that could modify the vector, size() would be freshly evaluated each time through the loop; if not it would be evaluated just once at the beginning.



In an ideal world, all these would result in code that is about equally efficient:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <vector>
#include <iterator>
#include <numeric>

int range_for( const std::vector<int>& seq )
{
    int s = 0 ;
    for( int v : seq ) s += v ;
    return s ;
}

int classic_for( const std::vector<int>& seq )
{
    int s = 0 ;
    for( std::size_t i = 0 ; i < seq.size() ; ++i ) s += seq[i] ;
    return s ;
}

int classic_for_naive( const std::vector<int>& seq )
{
    int s = 0 ;
    const auto n = seq.size() ;
    for( std::size_t i = 0 ; i < n ; ++i ) s += seq[i] ;
    return s ;
}

int iterator( const std::vector<int>& seq )
{
    int s = 0 ;
    for( auto iter = std::begin(seq) ; iter != std::end(seq) ; ++iter ) s += *iter ;
    return s ;
}

int iterator_naive( const std::vector<int>& seq )
{
    int s = 0 ;
    const auto end = std::end(seq) ;
    for( auto iter = std::begin(seq) ; iter != end ; ++iter ) s += *iter ;
    return s ;
}

int algorithm_numeric( const std::vector<int>& seq )
{ return std::accumulate( std::begin(seq), std::end(seq), 0 ) ; }


In practice, there is a quality of implementation issue involved. With the GNU compiler, the loops which use iterators (range-based for, straight loop with iterators, algorithms) generate slightly more optimal code than the ones which use subscripts:

GCC 4.9, i386 with
-std=c++11 -pedantic-errors -Wall -Wextra -O3 -fomit-frame-pointer -c -S

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
__Z9range_forRKSt6vectorIiSaIiEE:
	movl	4(%esp), %eax
	movl	(%eax), %edx
	movl	4(%eax), %ecx
	xorl	%eax, %eax
	cmpl	%ecx, %edx
	je	L4
L3:
	addl	(%edx), %eax
	addl	$4, %edx
	cmpl	%edx, %ecx
	jne	L3
	rep ret
L4:
	rep ret


__Z11classic_forRKSt6vectorIiSaIiEE:
	pushl	%ebx
	movl	8(%esp), %eax
	movl	(%eax), %ebx
	movl	4(%eax), %ecx
	subl	%ebx, %ecx
	sarl	$2, %ecx
	testl	%ecx, %ecx
	je	L9
	xorl	%edx, %edx
	xorl	%eax, %eax
L8:
	addl	(%ebx,%edx,4), %eax
	addl	$1, %edx
	cmpl	%ecx, %edx
	jne	L8
	popl	%ebx
	ret
L9:
	xorl	%eax, %eax
	popl	%ebx
	ret


__Z18classic_for_naiiveRKSt6vectorIiSaIiEE:
	pushl	%ebx
	movl	8(%esp), %eax
	movl	(%eax), %ebx
	movl	4(%eax), %ecx
	subl	%ebx, %ecx
	sarl	$2, %ecx
	testl	%ecx, %ecx
	je	L14
	xorl	%edx, %edx
	xorl	%eax, %eax
L13:
	addl	(%ebx,%edx,4), %eax
	addl	$1, %edx
	cmpl	%ecx, %edx
	jne	L13
	popl	%ebx
	ret
L14:
	xorl	%eax, %eax
	popl	%ebx
	ret


__Z8iteratorRKSt6vectorIiSaIiEE:
	movl	4(%esp), %eax
	movl	(%eax), %edx
	movl	4(%eax), %ecx
	xorl	%eax, %eax
	jmp	L17
L18:
	addl	(%edx), %eax
	addl	$4, %edx
L17:
	cmpl	%ecx, %edx
	jne	L18
	rep ret


__Z15iterator_naiiveRKSt6vectorIiSaIiEE:
	movl	4(%esp), %eax
	movl	4(%eax), %ecx
	movl	(%eax), %edx
	xorl	%eax, %eax
	cmpl	%edx, %ecx
	je	L22
L21:
	addl	(%edx), %eax
	addl	$4, %edx
	cmpl	%edx, %ecx
	jne	L21
	rep ret
L22:
	rep ret


__Z17algorithm_numericRKSt6vectorIiSaIiEE:
	movl	4(%esp), %eax
	movl	4(%eax), %ecx
	movl	(%eax), %edx
	xorl	%eax, %eax
	cmpl	%edx, %ecx
	je	L26
L25:
	addl	(%edx), %eax
	addl	$4, %edx
	cmpl	%edx, %ecx
	jne	L25
	rep ret
L26:
	rep ret


The mileage may vary with other implementations; for instance, IIRC, with an old version of the Microsoft compiler the subscript based versions generated slightly more optimal code.
Topic archived. No new replies allowed.