2

As far as I know, declaring a variable inside a loop is less efficient than declaring outside and modify it inside the loop.

Example:

std::list<<double> l;
for(int i = 0; i < 100000; ++i)
{
    double a;
    a = 500.0 * i;
    l.append(a);
}

Another example with pointer:

std::list<<double *> l;
for(int i = 0; i < 100000; ++i)
{
    double* a;
    *a = 500.0 * i;
    l.append(a);
}

The examples don't have enough sense but I just want to show that the double and the pointer are being declared inside the loop.

The thing is, the scope of the variable is the same as the loop, so when the loop do an iteration, will it destroy the variable and then declaring it again? Or it just stays in the heap until the end of the for loop? How efficient is it to do that? Is it a waste of resources?

I code it as it was in C++.

Thanks in advance!

TonySalimi
  • 8,257
  • 4
  • 33
  • 62
Monetillo
  • 29
  • 5
  • 2
    It'll be on the stack not the heap (or probably just in a register, not in main memory at all) and with (and probably without) optimisations enabled will generate identical code – Alan Birtles Sep 08 '22 at 07:47
  • 1
    Your assumption is (generally) incorrect. However, what *is* unadvisable is to split declaration and initialisation. – Konrad Rudolph Sep 08 '22 at 07:47
  • Always your first consideration should be what makes most sense, not what is most efficient. If a variable is used inside a loop and is reinitialised on each iteration of the loop then declare it inside the loop. – john Sep 08 '22 at 07:50
  • My rule of thumb: use the smallest scope possible. If the variable is used by code before OR after the loop then define the variable before the loop. If the variable is only needed in the loop, but its value needs to be carried between loop iterations (e.g. the value in any iteration depends on value it had in a previous iteration) then define it in the loop construct itself (`for (int i = 0; ...` is a basic example, in which `i` is used to control the loop). If the variable is only needed in the loop body, and its value doesn't depend on previous iterations, define it in the loop body. – Peter Sep 08 '22 at 07:51
  • It's not so much about minimising resource usage (unless the variable is a humongous object that is expensive to initialise or destroy AND testing/profiling reveals a significant overhead of that). It's more about ensuring that code is not permitted to access/modify a variable that it does not need to access/modify. – Peter Sep 08 '22 at 07:53
  • Another thing: there’s virtually *no* situation where `std::list` is the appropriate type on modern hardware: you can always replace it with `std::vector` and get better performance, because `std::list` has terrible cache locality, and performance on modern hardware crucially relies on cache locality. – Konrad Rudolph Sep 08 '22 at 07:56
  • 1
    [What exactly is the "as-if" rule?](https://stackoverflow.com/questions/15718262/what-exactly-is-the-as-if-rule) – 463035818_is_not_an_ai Sep 08 '22 at 07:57
  • If it makes sense to hoist the variable out of the loop, then any optimizing compiler will do that automatically. Write for clarity. Don't try to micro optimize like this. – Jesper Juhl Sep 08 '22 at 08:04
  • *As far as i know, declaring a variable inside a loop is less efficient than declaring outside and modify it inside the loop.* -- This isn't the 1980's. Optimizing compilers these days know good and well to move the declaration outside the loop if it detects that this could be the case. – PaulMcKenzie Sep 08 '22 at 08:06
  • The reason why I bring up the 1980's is that back then, many programmers would write code that could "beat the compiler at speed". Many of those coding tactics worked (fancy pointer manipulation, etc), as compiler optimization was not as robust as it is now. The issue is that many programmers still resort to these tricks, thinking that they can beat the optimizer. What happens is that the code is *slower* than it should be instead of faster, because the optimizer just gave up trying to optimize the code due to the user's code being too convoluted with pointers and what-not. – PaulMcKenzie Sep 08 '22 at 19:57

3 Answers3

3

In terms of memory usage, there is no difference in most implementations. A stack frame is typically created to accommodate all variables declared anywhere inside the function.

But if the type has a constructor and/or destructor, there might be additional overhead in calling it every loop iteration. And if that type allocates memory of its own (e.g. std::vector), then those allocations and deallocations are also going to happen each loop iteration, instead of only once.

That said, you should still start out by declaring the variable inside the loop if it's not needed for longer than one iteration! It's easy to forget to clear any previous value, and this can quickly lead to bugs. Much better to make it correct first, and optimize afterwards.

Thomas
  • 174,939
  • 50
  • 355
  • 478
  • @KonradRudolph You're right, edited. – Thomas Sep 08 '22 at 08:31
  • Thank you so much! So it will depend on how much effort it needs to put into declaring and deleting the variable – Monetillo Sep 08 '22 at 09:22
  • @Monetillo -- minor point: declaring and **destroying** the variable. Deleting is entirely different: if you create an object with `new` you destroy it with `delete`. – Pete Becker Sep 08 '22 at 12:52
1

As a general rule, I would say postpone variable definition as long as possible. It will help you to have clean code and also more efficient programs.

But for the case of loops, the story is a little bit different. Suppose that we have two loops like this:

// loop 1 - define variable outside the loop
MyType t;
for(auto x : x_container)
{
   t = DoSthAndGetT(x);
   // the rest of the code
}

// loop 2 - define variable inside the loop
for(auto x : x_container)
{
   MyType t(DoSthAndGetT(x));
   // the rest of the code
}

For loop 1 you have the calls for one constructor, one destructor and n assignments. For loop 2 you have n constructors and n destructors

So, considering the heaviness of your constructor, destructor and assignments, you can decide about the approach to select.

TonySalimi
  • 8,257
  • 4
  • 33
  • 62
  • Thank you so much! So if the cpu works more when you declare the variable inside the loop, it is less efficient, didn't it? – Monetillo Sep 08 '22 at 09:05
  • @Monetillo Well judging based on CPU usage is not that easy, because you cannot understand which part of the code inside the loop uses the cpu. As I proposed in the answer, take a deep look inside your ctor, dtor and assignment operator of your type and decide based on that. – TonySalimi Sep 08 '22 at 09:25
1

First of all

double* a;
*a = 500.0 * i;

Is outright wrong. It invokes undefined behavior because a is not pointing to a double. Don't write code like this. Don't expect pointers to do magic. Pointers point somewhere, thats what they do. To store a double you need a double, not just a pointer.


Don't be afraid to introduce a temporary double inside a loop. A good compiler will notice that there is absolutely no difference in the end result whether you wrote the code like you did or the code is this:

std::list<double> l;
for(int i = 0; i < 100000; ++i)
{
    l.append(500.0 * i);
}

Hence, I would expect the compiler to emit the same output for any of the three versions. See What exactly is the "as-if" rule?.

You should rather write code for clarity and readability.

Do you want to use the same a in each iterator or do you want a different a in each iteration? Thats what decides to declare a inside or outside the loop. You can introduce a scope to limit the scope of a to be as narrow as possible even when declaring it outside of the loop:

 std::list<double> l;
 { 
     double a = 0.0;
     for (int i = 0; i <100000; ++i) {
          l.append(a);
          a += 500.0;
     }
 }
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Thank you so much! In case of the pointer example, Will it act the same? – Monetillo Sep 08 '22 at 09:19
  • @Montellio woah actually I overlooked the issue with that. `double* a` is just a pointer and `*a` is undefined behavior. You never allocated the `double` where you could store a value. – 463035818_is_not_an_ai Sep 08 '22 at 09:21
  • Okey I didn't write the example pointer good, but what i want to represent is that you have a loop, inside it you declare a pointer, do something with it and then it is stored in somewhere(a list or a vector). – Monetillo Sep 08 '22 at 09:40
  • @Monetillo I do understand what you wanted to illustrate. Thats what I was refering to with the initial answer. However, once you do dynamically allocate memory its a different story. I am not sure if allocating memory can be optimized away. I remeber something that this isnt the case – 463035818_is_not_an_ai Sep 08 '22 at 09:41
  • @Monetillo if you make the pointer point to some `double` that is allocated outside of the loop then I'd expect it to be the same as with `double a;`, though it might be more difficult for the compiler to see that nothing inside the loop modifies the value that pointer is pointing to. – 463035818_is_not_an_ai Sep 08 '22 at 09:43