Inline my Vector Class functions

Started by
11 comments, last by iMalc 12 years, 7 months ago
Hi,

I understand what the inline keyword does in C++ but am a bit confused where and where not to use it.
So I have my Vector3 class I made -



#pragma once
#include <iostream>
#include "cVector4.h"

template <class T>
class cVector3
{
public:
T x, y, z;

// Constructors
cVector3(void) : x(0), y(0), z(0) { } // Set all values to zero as default
cVector3(T argX, T argY, T argZ) : x(argX), y(argY), z(argZ) { }
cVector3(const cVector4<T> &vec4) : x(vec4.x), y(vec4.y), z(vec4.z) { }
~cVector3(void) { } // Destructor

// Copy constructor
cVector3 (const cVector3& other) : x (other.x), y (other.y), z (other.z) { }

void operator = (const cVector3 &argVector)
{ x = argVector.x; y = argVector.y; z = argVector.z; }

void operator = (const float& argFloat)
{ x = argFloat; y = argFloat; z = argFloat; }

// Negative vector
cVector3 operator - (void) const
{
return cVector3 (-x, -y, -z);
}

// Vector - Vector assignment operators
void operator += (const cVector3 &argVector) { x += argVector.x; y += argVector.y; z += argVector.z; }
void operator -= (const cVector3 &argVector) { x -= argVector.x; y -= argVector.y; z -= argVector.z; }
void operator *= (const cVector3 &argVector) { x *= argVector.x; y *= argVector.y; z *= argVector.z; }
void operator /= (const cVector3 &argVector) { x /= argVector.x; y /= argVector.y; z /= argVector.z; }

// Bool == Operator
bool operator == (const cVector3 &argVector)
{
if(x == argVector.x &&
y == argVector.y &&
z == argVector.z)
{
return true;
}
return false;
}

// Vector - Vector operators
cVector3 operator + (const cVector3 &argVector) const { return cVector3(x + argVector.x, y + argVector.y, z + argVector.z); }
cVector3 operator - (const cVector3 &argVector) const { return cVector3(x - argVector.x, y - argVector.y, z - argVector.z); }
cVector3 operator * (const cVector3 &argVector) const { return cVector3(x * argVector.x, y * argVector.y, z * argVector.z); }
cVector3 operator / (const cVector3 &argVector) const { return cVector3(x / argVector.x, y / argVector.y, z / argVector.z); }

// Vector - Value assignment operators
void operator += (T value) { x += value, y += value, z += value; }
void operator -= (T value) { x -= value, y -= value, z -= value; }
void operator *= (T value) { x *= value, y *= value, z *= value; }
void operator /= (T value) { x /= value, y /= value, z /= value; }

// Vector - Value operators
cVector3 operator + (T value) const { return cVector3(x + value, y + value, z + value); }
cVector3 operator - (T value) const { return cVector3(x - value, y - value, z - value); }
cVector3 operator * (T value) const { return cVector3(x * value, y * value, z * value); }
cVector3 operator / (T value) const { return cVector3(x / value, y / value, z / value); }

// Dot product
T Dot(const cVector3 &argVector) const { return x * argVector.x + y * argVector.y + z * argVector.z; }
// Cross product
cVector3 Cross(cVector3 &argVector)
{
return Vector3f(
y * argVector.z - z * argVector.y,
z * argVector.x - x * argVector.z,
x * argVector.y - y * argVector.x);
}
// Returns the length squared
T LengthSquared() { return x * x + y * y + z * z; }
// Returns the length of the vector
T Length() { return sqrt(LengthSquared()); }
// Sets the length of the vector
void SetLength (T vecLength)
{
Normalise();
*this *= vecLength;
}
// Normalises the vector (i.e makes it a unit vector)
void Normalise()
{
T vecLength = Length();
if(vecLength != 0)
{
*this /= vecLength;
}
}

void Zero()
{
x = 0; y = 0; z = 0;
}

// Prints the x, y and z to console
void PrintVector()
{
std::cout<< x << ", " << y << ", " << z << std::endl;
}

// Convert to Vector4
cVector4<T> ToVector4()
{
return cVector4<T>(x, y, z, 1);
}

operator cVector4<T>() const // Convert to vec 4
{
return cVector4<T>(x, y, z, 1);
}

};



// Types
typedef cVector3 <float> Vector3f;
typedef cVector3 <double> Vector3d;
typedef cVector3 <int> Vector3i;




So I put the inline keyword infront of the first lot of operators -



// Vector - Vector assignment operators
inline void operator += (const cVector3 &argVector) { x += argVector.x; y += argVector.y; z += argVector.z; }
inline void operator -= (const cVector3 &argVector) { x -= argVector.x; y -= argVector.y; z -= argVector.z; }
inline void operator *= (const cVector3 &argVector) { x *= argVector.x; y *= argVector.y; z *= argVector.z; }
inline void operator /= (const cVector3 &argVector) { x /= argVector.x; y /= argVector.y; z /= argVector.z; }




which gave me a boost in the performance of my program so I thought "sweet I'll do this for all the operators" like so -



#pragma once
#include <iostream>
#include "cVector4.h"

template <class T>
class cVector3
{
public:
T x, y, z;

// Constructors
cVector3(void) : x(0), y(0), z(0) { } // Set all values to zero as default
cVector3(T argX, T argY, T argZ) : x(argX), y(argY), z(argZ) { }
cVector3(const cVector4<T> &vec4) : x(vec4.x), y(vec4.y), z(vec4.z) { }
~cVector3(void) { } // Destructor

// Copy constructor
cVector3 (const cVector3& other) : x (other.x), y (other.y), z (other.z) { }

void operator = (const cVector3 &argVector)
{ x = argVector.x; y = argVector.y; z = argVector.z; }

void operator = (const float& argFloat)
{ x = argFloat; y = argFloat; z = argFloat; }

// Negative vector
cVector3 operator - (void) const
{
return cVector3 (-x, -y, -z);
}

// Vector - Vector assignment operators
inline void operator += (const cVector3 &argVector) { x += argVector.x; y += argVector.y; z += argVector.z; }
inline void operator -= (const cVector3 &argVector) { x -= argVector.x; y -= argVector.y; z -= argVector.z; }
inline void operator *= (const cVector3 &argVector) { x *= argVector.x; y *= argVector.y; z *= argVector.z; }
inline void operator /= (const cVector3 &argVector) { x /= argVector.x; y /= argVector.y; z /= argVector.z; }

// Bool == Operator
inline bool operator == (const cVector3 &argVector)
{
if(x == argVector.x &&
y == argVector.y &&
z == argVector.z)
{
return true;
}
return false;
}

// Vector - Vector operators
inline cVector3 operator + (const cVector3 &argVector) const { return cVector3(x + argVector.x, y + argVector.y, z + argVector.z); }
inline cVector3 operator - (const cVector3 &argVector) const { return cVector3(x - argVector.x, y - argVector.y, z - argVector.z); }
inline cVector3 operator * (const cVector3 &argVector) const { return cVector3(x * argVector.x, y * argVector.y, z * argVector.z); }
inline cVector3 operator / (const cVector3 &argVector) const { return cVector3(x / argVector.x, y / argVector.y, z / argVector.z); }

// Vector - Value assignment operators
inline void operator += (T value) { x += value, y += value, z += value; }
inline void operator -= (T value) { x -= value, y -= value, z -= value; }
inline void operator *= (T value) { x *= value, y *= value, z *= value; }
inline void operator /= (T value) { x /= value, y /= value, z /= value; }

// Vector - Value operators
inline cVector3 operator + (T value) const { return cVector3(x + value, y + value, z + value); }
inline cVector3 operator - (T value) const { return cVector3(x - value, y - value, z - value); }
inline cVector3 operator * (T value) const { return cVector3(x * value, y * value, z * value); }
inline cVector3 operator / (T value) const { return cVector3(x / value, y / value, z / value); }

// Dot product
T Dot(const cVector3 &argVector) const { return x * argVector.x + y * argVector.y + z * argVector.z; }
// Cross product
cVector3 Cross(cVector3 &argVector)
{
return Vector3f(
y * argVector.z - z * argVector.y,
z * argVector.x - x * argVector.z,
x * argVector.y - y * argVector.x);
}
// Returns the length squared
T LengthSquared() { return x * x + y * y + z * z; }
// Returns the length of the vector
T Length() { return sqrt(LengthSquared()); }
// Sets the length of the vector
void SetLength (T vecLength)
{
Normalise();
*this *= vecLength;
}
// Normalises the vector (i.e makes it a unit vector)
void Normalise()
{
T vecLength = Length();
if(vecLength != 0)
{
*this /= vecLength;
}
}

void Zero()
{
x = 0; y = 0; z = 0;
}

// Prints the x, y and z to console
void PrintVector()
{
std::cout<< x << ", " << y << ", " << z << std::endl;
}

// Convert to Vector4
cVector4<T> ToVector4()
{
return cVector4<T>(x, y, z, 1);
}

operator cVector4<T>() const // Convert to vec 4
{
return cVector4<T>(x, y, z, 1);
}

};



// Types
typedef cVector3 <float> Vector3f;
typedef cVector3 <double> Vector3d;
typedef cVector3 <int> Vector3i;




Is this correct, I know that the book I was reading said to be careful when using the inline keyword as it is easy to think that inlining everything with make your program run faster but it won't. If someone could advise roughly where and where not to use it would be helpful. Also should I use it on the other functions?

Many Thanks
x

"To know the road ahead, ask those coming back."

Advertisement
Inlining can make can make the code size bigger and therefore less cache friendly which can hurt performance. This is more of a problem if you inline large functions.

Functions that are defined inside the class body is implicit inline so no need to use the inline keyword there. Note that the compiler is free to ignore the inline keyword and make it's own decision on what functions to inline and not to inline.
Thanks Wooh,

So your saying that I do not need to use the inline keyword at all in this class and the compiler will do it for me?

Because I found this example on the net -
http://www.3dkingdoms.com/weekly/vec3.h


And they have inlined some of their functions.

And the book I was reading was saying that the inline keyword was more of a hint to the compiler and it could ignore it without any warning.

"To know the road ahead, ask those coming back."

So your saying that I do not need to use the inline keyword at all in this class and the compiler will do it for me?
Yes. As long as the function is defined inside the class definition there is no need to use the inline keyword.

Because I found this example on the net -
http://www.3dkingdoms.com/weekly/vec3.h

And they have inlined some of their functions.
inline here shouldn't make a difference.

And the book I was reading was saying that the inline keyword was more of a hint to the compiler and it could ignore it without any warning.
yes, but it still have to follow all the special rules for inline functions. You are allowed to define an inline function in multiple translation units but a normal function you can't do that as an example.
Brilliant, Thanks Wooh,

I will bear this in mind.

"To know the road ahead, ask those coming back."

Basically to use it correctly, you never decide where it goes in advance. You only ever put it in in places where profiling tells you that the function call is taking too much time, or that by looking at the disassembly you can see that the size of the code to make the call is about the same size as inlining the function call itself would be.
You then proceed to inlining it, and only if that improves the situation do you leave it in there.

Generally though you almost never need to use it. Your compiler should be able to do a pretty good job on its own if you set the right optimisation settings.

By the way, about that code: cVector3(void) : x(0), y(0), z(0) { } // Set all values to zero as default(void) is an old C throwback. In C++ we just use empty parenthesis. Clearly since C has no constructors or destructors it's not like this can be compiled by a C compiler anyway.

Also, you should avoid declaring the assignment operator, copy-constructor, and destructor when the compiler generated one is sufficient. ~cVector3(void) { } // Destructor

// Copy constructor
cVector3 (const cVector3& other) : x (other.x), y (other.y), z (other.z) { }

void operator = (const cVector3 &argVector)
{ x = argVector.x; y = argVector.y; z = argVector.z; }
When you let the compiler generate these on its own then they will end up either more efficient or equally as efficient. The compiler might even make a better decision on inlining the ones it generates on its own. IMHO, the "Rule of Three" is as much about implementing none of these for some classes, as it is about implementing all of these in others.

void PrintVector()
{
std::cout<< x << ", " << y << ", " << z << std::endl;
}
[/quote]Rather than doing this, a better approach is to overload the << operator for the class. That way you can do:cVector3 v(1,2,3);

cout << v;
Not to mention that it then means you can send it to other string streams as well rather than just to cout.

You could improve the efficiency of your SetLength function by first calculating the length and then calculating the scale factor (new length divided by the old length). Then multiply by the scale factor instead. It saves essentially calculating the scale factor three times, and also saves writing back to the vector twice.
You could also multiply by the reciprocal in your operators that use divisions. Calculating 1.0/x plus using three multiplications by that, is faster than just performing 3 divides, since divides are slow.

Better yet though, I hear really good things about the CML library.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms

Also, you should avoid declaring the assignment operator, copy-constructor, and destructor when the compiler generated one is sufficient.


I really want to emphasis on this point. When I was implementing my raytracer, I observed in practice a bug in VS2008 where when I had the following code




class KdTreeTraversalNode

{

public:

KdTreeTraversalNode() {}

~KdTreeTraversalNode() {} // (1)

// Has only members with trivial ctors and dtors (POD types).

};




void TravelKdTree()

{

KdTreeTraversalNode temp[100]; // Disassembly shows a call to compiler-generated array initializer which invokes object ctors.

// At the end of scope, disassembly shows calls to the dtors, even when they're trivial.

}



I could see in the disassembly that there was a call to an array initializer function even when the ctor and dtor were empty, although manually defined. If I deleted the empty destructor, i.e. commented out line (1) (but I could keep the empty ctor line), then the compiler was smart enough to omit the initializer calls.

Commenting out that single line boosted my raytracer performance by several hundred thousand of rays per second, so I was really happy to catch that by reading the disassembly!
Cool thanks for the replies and taking a sec to look over my class.

I didn't realise that commenting out the destructor could actually boost performance, because I use Visual Studio and whenever I create a new class it always puts in the constructor and destructor for me I have always left it there. I have heard of the rule of 3 before but never thought it could actually worsen performance. I shall remember this for the rest of my programming.

And iMalc, when you say empty [color="#1C2837"]parenthesis you mean -

[color="#1C2837"]
cVector3() : x(0), y(0), z(0) { } // Set all values to zero as default



[color="#1C2837"]because again that is just put there by Visual Studio when I create a new class, I don't normally put the void word in when making a function with no arguments. Will it change performance in anyway if I have it there?

[color="#1C2837"]I've now got rid of the [color="#1C2837"]assignment operator, copy-constructor, and destructor and am going to make the changes you suggest to the print function so it overloads the << operator and making my SetLength and divide functions run slightly faster.

[color="#1C2837"]Thanks for all the advice.
[color="#1C2837"]x

"To know the road ahead, ask those coming back."


I didn't realise that commenting out the destructor could actually boost performance, because I use Visual Studio and whenever I create a new class it always puts in the constructor and destructor for me I have always left it there. I have heard of the rule of 3 before but never thought it could actually worsen performance. I shall remember this for the rest of my programming.




Well, remember correctness before performance in any case. If you need a dtor for cleanup, never omit it (and there's no need in particular to try to design your implementation around the goal of 'no code in dtors'). Just if your dtor is empty, it doesn't hurt omitting it, and potentially avoiding this bug.

[color="#1C2837"]because again that is just put there by Visual Studio when I create a new class, I don't normally put the void word in when making a function with no arguments. Will it change performance in anyway if I have it there?




Having foo::foo(void) versus foo::foo() does not change anything.

In general, you can answer all these simple "is the line X or Y faster?" by digging through the disassembly, so better start learning it! It's very simple to get the asm output of a .cpp file in Visual Studio: right-click on a .cpp file, then choose Properties->C/C++->Output Files->Assembler Output: and select "Assembly with Source Code (/FAs)". Now when you compile your .cpp file again (ctrl-f7 to compile just a single file), VS will generate a .asm file to where Properties->C/C++->Output Files->ASM List Location points to.


The .asm file has the asm code and c++ code nicely interleaved, so you get a grasp of which code lines generated which assembly. It's very enlightening.




Thanks clb,

I've never done this before, it is very enlightening indeed, and maybe slightly overwhelming.
The problem with trying it with my Vector class is that all the code is in the header and there does not seem to be this option for header files. I presume this is to do with how they are compiled or something.

I had a look through one of my other classes though and thought to myself - "I am so glad I don't have to write games in assembler."
Next time I have a situation like this I will check the output first to see if I can answer the question by myself.

Cheers
x

"To know the road ahead, ask those coming back."

This topic is closed to new replies.

Advertisement