which one is better?

1
2
3
4
5
6
7

size_t temp;
for(size_t i = 0; i < 1000; ++i)
{ 
 temp = i;
//do something
}


1
2
3
4
5
for(size_t i = 0; i < 1000; ++i)
{ 
 size_t temp = i;
//do something
}


what are their advantages and disadvantages?
Thanks
in the second one, you won't be able to use temp outside the for loop. there is no other difference.
Last edited on
Well there is two things. One is what hamsterman said, in the second temp is now local to the loop and cannot be access outside it.

The second difference is that the second one recreates temp every run of the loop. This for native types like int and doubles this won't be a large choke. If you use this with creating a class and dynamic memory with large allocations it can slow down over time and add time to the execution.
Thanks
So the first choice would be a faster one compare to the second choice even it is native types?
Not with a good compiler. Most modern compilers should (with optimization enabled) generate output that will just initialize it once. If you want the advantages of both (keeping the variable local, yet not having to initialize it everytime) you could do this:
1
2
3
4
5
6
7
8
{ // This puts the initialization and the loop in a new scope
   size_t temp;
   for(size_t i = 0; i < 1000; ++i)
   {
      temp = i;
      // do something
   }
}


That should force any compiler to do what I said.
whoops. my bad..
the second is (very slightly) slower only when the object has a constructor or destructor. they will be called on every cycle. also, you can't rely temp to have the same state it had on the previous cycle.
Thank you very much
This topic has come up before.

The general rule is to minimize scope of declarations except where performance is adversely affected. The problem is that most people don't understand when that is. Take this example:

1
2
3
4
5
6
7
8
9
10
11
12
13
for( size_t file = 0; file < numFilesInDir; ++file ) {
    std::vector< std::string > lines;
    std::ifstream                     input_file( "file_" + boost::lexical_cast<std::string>( file ) + ".txt" );

    while( input_file ) {
        std::string text_line;
        std::getline( input_file, text_line );
        if( input_file )
            lines.push_back( text_line );
    }

    std::for_each( lines.begin(), lines.end(), std::cout << "Contents of file:\n" << boost::lambda::_1 << '\n' );
}


Now, let's make a concrete example. Say there are 2 files in the directory, both containing 5 lines of text.
What does the above program do, with respect to the std::vector<std::string> and the std::string inside
the while loop?

First, let's just look at the while loop, which will run 6 times for a 5-line file (the last time it will not do push_back).
Each of those six times, it first default constructs the std::string, then assigns it a value (std::getline) and then
destroys it. What is default construction? Well, construction in general means setting up the vtable for the object
and calling the constructor. Since std::string() has no virtual functions, there is no vtable to set up, thus that part
is a no-op. The constructor is going to initialize the string to an empty string, which means it will not allocate any
memory.

Next, for assigning it a value. Since the string is already empty, there is no memory to deallocate, so .clear() will
do nothing [if std::getline() calls it to empty the string] and operator=() will just allocate memory and copy the
source string into the object.

Last, for destroying it. Destruction means calling the destructor, which in our case means freeing the memory
allocated to the object, and then destroying the vtable, of which there is none.

Oh, and one more thing. Technically speaking a stack variable that is created would cause the compiler to allocate
stack space (usually just incrementing/decrementing a single CPU register) and one that is destroyed would cause
the compiler to free the stack space allocated to it (a decrement/increment of a single CPU register), however any
reasonable compiler will optimize both of those operations out of the while loop altogether.

So, in summary, the above code actually does this:
1. Allocate stack space exactly once [ before entering the while loop ]
2. Set all internal data members of std::string (default construct) 6 times [ default construction ]
3. Allocate memory and assign a string 5 times [ std::getline ]
4. Deallocate memory 5 times [ destruction ]
5. Deallocate stack space exactly once [ after exiting the while loop ]

Now, let's "optimize" the loop by moving the declaration of text_line outside the while loop:

1
2
3
4
5
6
    std::string text_line;
    while( input_file ) {
        std::getline( input_file, text_line );
        if( input_file )
            lines.push_back( text_line );
    }


We default construct text_line once, obviously. The first time through the loop, text_line has to be empty, so
the first call to std::getline() just has to allocate memory and copy the target into it. The next four times through
the loop, std::getline() will actually read the remaining lines from the file, and thus it needs first to deallocate the
memory held by the string from the previous iteration and then reallocate memory to store the new one. The
final time through the loop, std::getline() will deallocate the memory held by the string but won't read anything,
so no memory will be allocated. Then we destroy the string, which will be empty.

In summary:

1. Allocate stack space exactly once [ before entering the while loop ]
2. Set all internal data members of std::string (default construct) 1 times [ default construction ]
3. Deallocate memory and set all internal data members of std::string 5 times [ first half of std::getline ]
3. Allocate memory and assign a string 5 times [ second half std::getline ]
4. Call destructor, which will do nothing since string was already empty [ destruction ]
5. Deallocate stack space exactly once [ after exiting the while loop ]

Let's compare results:

Both allocate and deallocate stack space once.
Both perform 5 memory allocations.
Both perform 5 memory deallocations.

In other words, they are effectively the same from a performance standpoint. You have to think about it.
operator=() for a type is almost always effectively the same thing as running a destructor followed by a
copy constructor, the only difference being that vtables are not set up and destroyed. However, two things
about that. First, none of the STL containers have vtables, since none have virtual methods, and second,
the relative CPU time spent performing vtable setup/destruction is insignificant compared to even a single
memory allocation.

But, you say, what about the std::vector<std::string> in the for loop()? Surely it's expensive to construct
and destroy it, right? Answer: no more than if you were to move the variable outside the for() loop, because
if you did that, you'd have to call clear() on the vector after each iteration. And what does clear do()? Well,
deallocates all the memory, just as destructor would, just as operator= would. When there is no vtable,
it is no more expensive running the destructor and constructor than it is to call clear().

** Lastly -- for the astute reader, I am aware of std::vector<>::reserve(), however I deliberately avoided
mentioning it here to keep the example simple. For the purposes of this discussion, I assume that only
a single memory allocation is needed, whereas in fact with std::vector<>'s implementation that is not the
case. Not only is that a simple fix here -- reserve( 5 ) or whatever -- but it affects both approaches equally.
Last edited on
I just ran the following two programs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Program 1 - text_line outside the while loop:
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

int main() {
    for( size_t i = 0; i < 1000; ++i ) {
        std::ifstream             input_file( "some-file-containing-28181-lines.txt" );
        std::vector<std::string>  lines;

        lines.reserve( 35000 );
        std::string text_line;
        while( input_file ) {
            std::getline( input_file, text_line );
            if( input_file )
                lines.push_back( text_line );
        }
    }
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Program 2 - text_line inside the while loop:
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

int main() {
    for( size_t i = 0; i < 1000; ++i ) {
        std::ifstream             input_file( "some-file-containing-28181-lines.txt" );
        std::vector<std::string>  lines;

        lines.reserve( 35000 );
        while( input_file ) {
            std::string text_line;
            std::getline( input_file, text_line );
            if( input_file )
                lines.push_back( text_line );
        }
    }
}


I ran these programs four times, measuring CPU time for each run:


Program 1.  Outside while loop:

real    0m11.461s
user    0m10.266s
sys     0m1.175s

real    0m11.417s
user    0m10.270s
sys     0m1.143s

real    0m11.456s
user    0m10.177s
sys     0m1.275s

real    0m11.402s
user    0m10.275s
sys     0m1.125s

Average user time is about 10.27 seconds

Program 2:  inside the while loop:
real    0m8.965s
user    0m7.887s
sys     0m1.075s

real    0m9.030s
user    0m8.082s
sys     0m0.947s

real    0m9.036s
user    0m8.035s
sys     0m1.000s

real    0m9.023s
user    0m7.998s
sys     0m1.023s

Average user time is about 8.00 seconds


Notice that putting the variable INSIDE the while loop actually made the program run more than 20% FASTER.

This was compiled using g++ -O3


 g++ --version
g++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-48)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.


(Note 1. I have no doubt without trying that at -O0 program 2 will be much, much slower.)

(Note 2. I ran the program more than once prior to collecting this data so that the entire file would be in
the page cache for all executions, to avoid the variable of disk access. I also did not touch the keyboard or
mouse while the programs were running so as to not affect execution times.)
great post, jsmith. Optimisation is something I find quite difficult because often I don't know what's going on behind the scenes in terms of construction and destruction.
Thanks a lot, jsmith
But there are some problems I may not agree with
And what does clear do()? Well,
deallocates all the memory, just as destructor would, just as operator= would


According to the book, member function clear would not deallocate the memory which already
allocated, it would just clear the contents of string or vector and set their size back to zero
the capacity of them would not be change, because reallocate of memory could be very expensive
(After I enable C++0x of my gcc4.5, vector pop up a new member function--shrink to fit, maybe
this is a new feature of the C++0x?)
Besides, the memory of vector or string would not reallocate or deallocate inside the loop
if they declare outside of the loop and their capacity are large enough to hold the variables
Maybe I made a mistake about your words, if there are any errors about my thought, I am sorry for that

below is the code I compile with the release mode(O2) by gcc4.5(minGW)
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
  std::string A;
  cout<<"string A\n";
  size_t i;
  for(i = 0; i < 5; ++i)
  {
    cout<<"round = "<<i<<", size = "<<A.size()<<", capacity = "<<A.capacity()<<" contents = "<<A<<"\n";
    A = "wahaha";
  }
  cout<<"out of the loop\nbefore clear\n";
  cout<<"round = "<<i<<", size = "<<A.size()<<", capacity = "<<A.capacity()<<" contents = "<<A<<"\n";
  A.clear();
  cout<<"out of the loop\nafter clear\n";
  cout<<"round = "<<i<<", size = "<<A.size()<<", capacity = "<<A.capacity()<<" contents = "<<A<<"\n";

  std::vector<size_t> B;
  cout<<"vector B\n";
  for(i = 0; i < 5; ++i)
  {
    cout<<"round = "<<i<<", size = "<<B.size()<<", capacity = "<<B.capacity()<<"\n";
    cout<<"contents = ";
    copy(B.begin(), B.end(), std::ostream_iterator<size_t>(cout, ", ") );
    cout<<"\n";
    B.push_back(i);
  }
  cout<<"out of the loop\nbefore clear\n";
  cout<<"round = "<<i<<", size = "<<B.size()<<", capacity = "<<B.capacity()<<"\n";
  cout<<"contents = ";
  copy(B.begin(), B.end(), std::ostream_iterator<size_t>(cout, ", ") );
  B.clear();
  cout<<"out of the loop\nafter clear\n";
  cout<<"round = "<<i<<", size = "<<B.size()<<", capacity = "<<B.capacity()<<"\n";
  cout<<"contents = ";
  copy(B.begin(), B.end(), std::ostream_iterator<size_t>(cout, ", ") );
  cout<<"\n";

  cout << "system pause" << endl;
  std::cin.get();


The other assumption is
The program2 would be faster because the std::string text_line;
may not construct again after i = 1
because you forgot to reset the flag of your input_file

Anyway, thanks for your suggestion
I would not declare a big object in the loop
But I feel confused about the native type before this post
Last edited on
Just another small note:

If you are using the value read-only or dereference an iterator, etc. a reference can be used for clarity without copying.

1
2
3
4
5
6
7
typedef vector< int > container;
container c;

for( container::iterator i = c.begin(); i != c.end(); ++i ) {
    int & n( *i );
    // use n
}


This can be really handy if there are nested containers and you want to refer to a short named variable rather something long and obscure like: *(var->first->second).
Last edited on
@stereoMatching:


The other assumption is
The program2 would be faster because the std::string text_line;
may not construct again after i = 1
because you forgot to reset the flag of your input_file

Anyway, thanks for your suggestion
I would not declare a big object in the loop


The assumption is wrong. First, the *only* difference between program 1 and 2 is the location of the declaration
of std::string text_line; the logic is otherwise identical. Second, the file is closed each time through the loop by
virtue of the destructor for ifstream.

Regarding the rest of your post, I wrote and ran these two programs:

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
// Program 1 - vector declaration outside the for loop, with corresponding call to .clear()
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

int main() {
    std::vector<std::string>  lines;
    for( size_t i = 0; i < 1000; ++i ) {
        std::ifstream             input_file( "some-file-with-28181-lines.txt" );

        lines.reserve( 35000 );
        while( input_file ) {
            std::string text_line;
            std::getline( input_file, text_line );
            if( input_file )
                lines.push_back( text_line );
        }
 
        lines.clear();
    }
}

// Program 2 - vector declaration inside for loop
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

int main() {
    for( size_t i = 0; i < 1000; ++i ) {
        std::ifstream             input_file( "some-file-with-28181-lines.txt" );
        std::vector<std::string>  lines;

        lines.reserve( 35000 );
        while( input_file ) {
            std::string text_line;
            std::getline( input_file, text_line );
            if( input_file )
                lines.push_back( text_line );
        }
    }
}


The results are:


Program 1:
real    0m8.540s
user    0m7.908s
sys     0m0.630s

real    0m8.495s
user    0m7.799s
sys     0m0.692s

real    0m8.484s
user    0m8.035s
sys     0m0.448s

Program 2:
real    0m9.136s
user    0m8.176s
sys     0m0.959s

real    0m9.215s
user    0m8.210s
sys     0m1.004s

real    0m9.171s
user    0m8.172s
sys     0m0.997s


Approximately 7.90 seconds for program 1 (declaration outside loop) and 8.18 seconds for program 2 (minimal scope).
Declaration of larger object is very slightly slower inside the loop (remember -- those times are for 1000 executions
each; so the average time difference is .28 seconds per 1000 runs, or .00028 seconds per run (ie, 0.28 milliseconds).
0.28 milliseconds per run is a 3.5% overall speed decrease.

Unless I was writing performance critical code or this function ran a gazillion times, I'd probably opt for the minimal
scope.

Thanks jsmith, my assumption of flag didn't clear is wrong

moorecm, thanks for your advice, this could save me some trouble when I have to write some generic code
Topic archived. No new replies allowed.