loop counters as class members? (memory usage)

Started by
10 comments, last by cozzie 10 years, 9 months ago

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.).

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

Advertisement
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.

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/

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.

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

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.

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

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.

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.

"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
Thanks, this really helps. Both in supporting the decision as in understanding why.

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

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)

This topic is closed to new replies.

Advertisement