0

I've found a lot of discussion about loop invariant values and whether to declare them outside the loop or not. That is not what I'm concerned about. I'm concerned about loop variant values and whether declaring them explicitly has any potential performance implications.

A coworker recently sent out a piece of code. Let's say T is a sequence of integers, be it list, vector, etc.

#include <time.h>
#include <algorithm>

template <typename T>
clock_t _check(T &cont, int cnt)
{
    clock_t starttime = clock();

    srand(27);
    while (cnt--)
    {
        int cur = (rand() << 10) | rand();
        cont.insert(std::find_if(cont.begin(), cont.end(), [cur](int i) { return i >= cur; }), cur);
    }
    return clock() - starttime;
}

When I saw this code I immediate thought that I would change while loop to

    T::iterator it = std::find_if(cont.begin(), cont.end(), [&](int i)
    {
        return i >= cur;
    });
    cont.insert(it, cur);

So basically, I would capture cur by reference, split the line into two parts, and some minor formatting differences. I don't think there's any potential performance downsides as far as the reference is concerned, but let me know if I'm wrong. What about the iterator? I prefer this because I think it more clearly illustrates the process, but am I accidentally making another temporary or taking away optimization opportunities?

I know what you're all going to say, profile and don't optimize prematurely. I know, I believe. The problem is that I come across the situation of declaring temporary variables in loops very frequently, and the variable may not be an iterator. What if it's a pointer, or some data structure that requires a deep copy? What if the function call gets this variable by reference to const versus value? It would be nice to have some insight on the theoretical implications so I can do it "the right way" immediately and not have to test all the time.

So, is there any downside to explicitly declaring temporary variables before they are used? Or does the compiler make these two completely equivalent? Maybe people like the fact that the first example is one line and mine is five?

EDIT: I forgot to explain why I thought of this. "C++ Coding Standards, 101 Rules, Guidelines, and Best Practices" by Sutter and Alexandrescu says in Item 9 that "It is not a premature optimization to reduce spurious temporary copies of objects, especially in inner loops, when doing so doesn't impact code complexity". Is this what they're talking about?

DSM
  • 99
  • 6
  • *"I don't think there's any potential performance downsides as far as the reference is concerned, but let me know if I'm wrong"* Some (many?) implementations just store the stack base pointer (EBP) if you capture by reference. Accessing the captured variables then will not be any slower. – dyp May 23 '14 at 22:57
  • `T::iterator` has to be `typename T::iterator` btw. But I'd rather use `auto const it = ..;` – dyp May 23 '14 at 22:59
  • Interesting, why does typename have to be there? – DSM May 23 '14 at 23:02
  • For a detailed explanation, see [Where and why do I have to put the “template” and “typename” keywords?](http://stackoverflow.com/q/610245/420683). The compiler knows that `T` is a type, but it doesn't know that `T::iterator` is a type when parsing the template. – dyp May 23 '14 at 23:18
  • Those numbers are not going to be very random. Use, uhm whatever's suitable for the purpose. Also I would just use a self-ordering container, or an explicit final sorting step. – Cheers and hth. - Alf May 24 '14 at 00:46
  • I suppose I should have made a minimal example. This application was just something someone sent out when discussing list vs. vector inserts in the middle of a sequence. – DSM May 24 '14 at 00:55

2 Answers2

0

The answer to your question is that there could be a performance hit if the copy constructor for the temporary variable is lengthy (e.g., copies a matrix or image) and cannot be optimized out (using inline).

Your second example calls

   std::find_if
   T::iterator copy constructor
   insert

Your first example calls

   std::find_if
   insert

In your specific example, the performance difference between the two is likely to be negligible. However, you could create a similar example where the image could be great.

user3344003
  • 20,574
  • 3
  • 26
  • 62
  • Yes, but is this something a compiler can optimize out? – DSM May 24 '14 at 01:56
  • If the copy constructor be inline and had no side effects, it could be optimized out – user3344003 May 24 '14 at 02:01
  • Hmm. I'm not sure I understand. The first example still implicitly calls the copy constructor, correct? Inlining would not seem to make these equivalent. Am I missing something? – DSM May 24 '14 at 03:01
  • The compiler might detect that the constructor has no side effects, see that the variable is only a temporary, then optimize it out. – user3344003 May 24 '14 at 03:06
  • Ok. One more thing. Give me an example side effect that could prevent this from being optimized out. – DSM May 24 '14 at 03:20
  • type::type (const type &src) { buffer = new char [src.bufferSize] ; . . . } – user3344003 May 24 '14 at 03:22
  • 1
    I might be totally off but I would have thought RVO would allow removal of copies regardless of side effects? – harmic May 24 '14 at 03:43
0

In very general terms, for a "good enough" compiler, you can always introduce a named local variable that substitutes for a compiler temporary with no impact on performance.

If you can satisfy yourself that by introducing a named local variable you are merely spelling out what the compiler was going to do anyway, then the compiler will (should) generate exactly the same code.

Obviously there are provisos on that. The named local variable should have the same lifetime, so that it can be kept in a register and not be forced into flushing to a memory location. There are situations where the compiler is permitted to delete (or inline) certain operations, and your substitution should not prevent it doing so.

In this particular case I believe the iterator will just be a pointer into the collection, so it should meet those provisos. There could be other cases where this is not so.

At this point I should make a plea for brevity. Some programmers prefer prolixity in the belief that more code makes it easier to read. I do not share that view. Your code should be as short as it possibly can be (but no shorter) and on those grounds the original is far better than your proposed changes, and also avoids the risk of confusing the compiler into generating worse code.

david.pfx
  • 10,520
  • 3
  • 30
  • 63