Jump to content

  • Log In with Google      Sign In   
  • Create Account

FREE SOFTWARE GIVEAWAY

We have 4 x Pro Licences (valued at $59 each) for 2d modular animation software Spriter to give away in this Thursday's GDNet Direct email newsletter.


Read more in this forum topic or make sure you're signed up (from the right-hand sidebar on the homepage) and read Thursday's newsletter to get in the running!


loop counters as class members? (memory usage)


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
11 replies to this topic

#1 cozzie   Members   -  Reputation: 1770

Like
0Likes
Like

Posted 23 June 2013 - 12:52 PM

Hi,

 

I'm currently 'implementing' correct usage of const function parameters and class member functions.

During this I'm running into the following:

 

- class 'MyClass' has several (unsigned) int variables, which are used throughout the member functions for for loops, counter etc.

- when I try to make one of these member functions 'const', which is logical looking at the functions itself, but this doesn't work, since the used counter variables are members of the class

 

The solution seems very simple, create local (unsigned) int variables for my loops within the member functions.

My question:

 

- would it be a bad idea, memory/ performance wise, to do this?

For example, my rendermesh and rendersubmesh functions have one or more for loops, using a class member for the for loop.

This would mean I 'make' a new integer in the local functions, for each submesh/ mesh I render in every frame.

 

Any input is really appreciated.

 

Example:

void CD3dmesh::RenderMesh(LPDIRECT3DDEVICE9 pD3ddev, D3DPRIMITIVETYPE pMethod) const
{
	for(sub=0;sub<mSubMeshSize;++sub)
	{
		pD3ddev->DrawIndexedPrimitive(pMethod, 
		 	  	  					  0,
								      mSubMeshTable[sub].VertexStart, 
								      mSubMeshTable[sub].VertexCount, 
								      mSubMeshTable[sub].FaceStart*3,
								      mSubMeshTable[sub].FaceCount); 
	}
}

Where 'sub' now is a class member. I could simply create a int sub (or something) locally in the function.

Maybe a stupid question, but will the memory of the int will be freed everytime the function finishes?

(would it be a good idea performance wise etc.).



Sponsor:

#2 SiCrane   Moderators   -  Reputation: 9673

Like
3Likes
Like

Posted 23 June 2013 - 12:56 PM

There's really no reason to make loop variables member variables. Local function variables will be automatically allocated and cleaned up every time the function is called and doing so is very cheap.

That said, if you do want to have a member variable that can be altered inside a const member function you can declare that member variable to be mutable in the class definition. An example of when you might want to do this is to cache the result of a complex computation.

#3 Matt-D   Crossbones+   -  Reputation: 1469

Like
3Likes
Like

Posted 23 June 2013 - 01:05 PM

Note that in C++11 you'd likely need to protect mutable members with synchronization (e.g., in case your code ever runs in a multithreaded context), see:
GotW #6a: http://herbsutter.com/2013/05/24/gotw-6a-const-correctness-part-1-3/
GotW #6b: http://herbsutter.com/2013/05/28/gotw-6b-solution-const-correctness-part-2/

In particular:

 

In general, remember:

Guideline: Remember the “M&M rule”: For a member variable, mutable and mutex (or atomic) go together.

This applies in both directions, to wit:

(1) For a member variable, mutable implies mutex (or equivalent): A mutable member variable is presumed to be a mutable shared variable and so must be synchronized internally—protected with a mutex, made atomic, or similar.

(2) For a member variable, mutex (or similar synchronization type) implies mutable: A member variable that is itself of a synchronization type, such as a mutex or a condition variable, naturally wants to be mutable, because you will want to use it in a non-const way (e.g., take a std::lock_guard<mutex>) inside concurrent const member functions.

 

 

More details:

http://isocpp.org/blog/2012/12/you-dont-know-const-and-mutable-herb-sutter
http://musingstudio.com/2013/05/28/herb-sutter-gotw6-const-and-mutable/

20130110-130953.jpg
http://musingstudio.com/2013/01/10/herb-sutter-video-you-think-you-know-const-and-mutable/
 


Edited by Matt-D, 23 June 2013 - 01:05 PM.


#4 rip-off   Moderators   -  Reputation: 8762

Like
6Likes
Like

Posted 23 June 2013 - 03:24 PM

Putting loop counters and other temporary variables into a class or struct is likely going to be more expensive than leaving it a local. As a local, the compiler will almost certainly not allocate any memory at all, but instead just reserve a register for the loop counter.

 

I'm not aware of modern compilers being smart enough to optimise away entire members in cases like this. If they can, then you're back to the above situation, more or less. If not, the extra memory consumed is not free, you'll pay that price every time such an object is loaded or saved between memory and the processor caches. The compiler will probably implement loops like the locals, but with an additional write to memory of the final loop counter value. 

 

Finally, there is the last cost: bugs. A counter like that will be considered to be initialised outside the constructor, so you won't get compiler errors for code like the following:

void SomeObject::someMemberFunction()
{
    // Whoops, forgot to reset counter!
    while(counter < N) 
    {
        // Do some work
    }
}

 

Now, where things get less clear is when the "temporary" variable is not a native or simple type, but something complex like std::string or std::vector. In some cases, creating new instances of such types might become prohibitive if you need such functionality in core loops. Re-using existing values might be a valid optimisation here, assuming there are no higher level algorithmic or data structure based optimisations available to avoid such work.



#5 Matt-D   Crossbones+   -  Reputation: 1469

Like
4Likes
Like

Posted 23 June 2013 - 03:30 PM

As for the original question, I agree with the others; two relevant principles here:

  • Minimizing Variable Scope
    Recall that programming is about communication with the maintainer of the code, more than about communication with the compiler.
    In order to help the maintainer as much as possible, we should make sure variables and other identifiers have as small a scope as possible.
    The scope of a variable is the physical program text in which it is visible. The smaller to scope of each variable, the fewer the live variables at any particular point in the code.

     
  • Reduce Scope of Variable
    // well, OK, this one is a refactoring used to follow the aforementioned principle :-)

     

    You have a local variable declared in a scope that is larger than where it is used

    Reduce the scope of the variable so that it is only visible in the scope where it is used

     


Edited by Matt-D, 23 June 2013 - 03:33 PM.


#6 cozzie   Members   -  Reputation: 1770

Like
0Likes
Like

Posted 23 June 2013 - 06:31 PM

Thanks all, my conclusions:

- making these variables mutable should work, but is tricky (as it is today akready), risk on faulty initializiation etc.
- since they're just simple ints and unsigned ints, no complex / higher level algorithms etc, I will create them all locally within the member functions and cleanup the ones in the class(es)

This also fits perfectly within the given principles.

#7 Álvaro   Crossbones+   -  Reputation: 13933

Like
4Likes
Like

Posted 23 June 2013 - 10:37 PM

The fact that these variables were not local variables to begin with smells of premature [and misguided] optimization. Give variables as small a scope as possible, unless you find really strong reasons not to.



#8 iMalc   Crossbones+   -  Reputation: 2314

Like
4Likes
Like

Posted 24 June 2013 - 01:06 AM

Thanks all, my conclusions:

- making these variables mutable should work, but is tricky (as it is today akready), risk on faulty initializiation etc.
- since they're just simple ints and unsigned ints, no complex / higher level algorithms etc, I will create them all locally within the member functions and cleanup the ones in the class(es)

This also fits perfectly within the given principles.

Don't even look at it as if you have an option or choice there. Misusing a member variable for this purpose has many dire consequences:

  1. It inhibits some optimisation because at the very least, the final value must needlessly be written back into the member variable, and worst case may even cause the compiler to not use a register where it otherwise could.
  2. Stack allocation is cheap, very cheap. So cheap that adding an extra variable your stack frame normally has absolutely zero impact on performance.
  3. As a member it increases the amount of memory your class occupies. With many instances, and/or on smaller objects, this could bump up RAM usage very significantly.
  4. It simply will not work when you try to have nested functions using the same member variable as the for-loop variable.
  5. Making it mutable requires that you also wear the added runtime cost of making it threadsafe.
  6. Trying to use any functions which use the same member variable as a loop counter from multiple threads simply will not work.

Long story short, do not ever misuse use a member variable for the purpose of what should be a local loop variable.


Edited by iMalc, 24 June 2013 - 01:07 AM.

"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms

#9 cozzie   Members   -  Reputation: 1770

Like
0Likes
Like

Posted 24 June 2013 - 04:34 AM

Thanks, this really helps. Both in supporting the decision as in understanding why.

#10 phantom   Moderators   -  Reputation: 7590

Like
1Likes
Like

Posted 24 June 2013 - 06:48 AM

Also, remember that with member variables you are effectively doing 'this->var' which, unless the compiler is able to prove can't be affected by other code, it is forced to produce sub-optimal code where it is likely to read-modify-write the value at the start of every loop iteration to ensure any other code looking at it gets the up to date version.

Same applies to end conditions, where accessing a member is going to, more than likely, cause reload from the 'end' variable.

This is why I prefer to structure my loops 'for(unsigned int idx = 0, end = m_count; idx < end; ++idx)' to give the compiler the best chance of removing redundant read/writes and get things into registers.

(member variables for loop counters would also invalidate cache lines where not required causing more memory traffic and core synchronization to happen and in world where memory bandwidth and latency is the biggest issue going this is bad voodoo)

#11 mhagain   Crossbones+   -  Reputation: 8281

Like
1Likes
Like

Posted 24 June 2013 - 08:37 AM

See http://stackoverflow.com/questions/161053/c-which-is-faster-stack-allocation-or-heap-allocation

 

 

Stack allocation is much faster since all it really does is move the stack pointer.

 

Since loop counters declared locally are allocated on the stack, this is your worst case scenario in terms of performance - a single pointer move (your best case will be just using a register, which your compiler may do automatically for you, or you may explicitly request with the "register" keyword).

 

For memory usage, and leaving aside the fact that you're getting dangerously into micro-optimization territory here, the stack will likewise be more efficient.  If you declare a loop counter as a class member, then the memory used for that loop counter will always be used, even when no loops are running.  Contrast with:

for (int i = 0; i < whatever; ++i)
{
}

In this case the memory is only used within the scope of the loop itself; allocation is (worst-case) just moving the stack pointer, freeing is also (worst case) just moving the stack pointer.  Best case your compiler is just going to take a register for it and just not use that register for anything else within the loop, which means it's effectively totally free: no allocation, no memory usage, fast.


It appears that the gentleman thought C++ was extremely difficult and he was overjoyed that the machine was absorbing it; he understood that good C++ is difficult but the best C++ is well-nigh unintelligible.


#12 cozzie   Members   -  Reputation: 1770

Like
0Likes
Like

Posted 24 June 2013 - 11:12 AM

Thanks, clear.
My thought on optimization was initially that some loops are used each frame and therefor the 'int' counters of the loop would take up that much memory. Not thinking about where the memory was and how quick it's gone, also not thinking about the constant needed communication and synchronization with the class members.

Also for readability I'll go for initializing the counter in the for loop alway, like mhagain showed in the example.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS