Jump to content

  • Log In with Google      Sign In   
  • Create Account

Inline my Vector Class functions


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
12 replies to this topic

#1 Monkan   Members   -  Reputation: 669

Like
0Likes
Like

Posted 03 September 2011 - 04:38 AM

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

Sponsor:

#2 Wooh   Members   -  Reputation: 695

Like
0Likes
Like

Posted 03 September 2011 - 05:00 AM

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.

#3 Monkan   Members   -  Reputation: 669

Like
0Likes
Like

Posted 03 September 2011 - 05:24 AM

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

#4 Wooh   Members   -  Reputation: 695

Like
0Likes
Like

Posted 03 September 2011 - 06:41 AM

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.

#5 Monkan   Members   -  Reputation: 669

Like
0Likes
Like

Posted 03 September 2011 - 07:06 AM

Brilliant, Thanks Wooh,

I will bear this in mind.
"To know the road ahead, ask those coming back."

#6 iMalc   Crossbones+   -  Reputation: 2338

Like
0Likes
Like

Posted 03 September 2011 - 02:40 PM

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;
}

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

#7 clb   Members   -  Reputation: 1806

Like
0Likes
Like

Posted 03 September 2011 - 04:01 PM

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!
Me+PC=clb.demon.fi | C++ Math and Geometry library: MathGeoLib, test it live! | C++ Game Networking: kNet | 2D Bin Packing: RectangleBinPack | Use gcc/clang/emcc from VS: vs-tool | Resume+Portfolio | gfxapi, test it live!

#8 Monkan   Members   -  Reputation: 669

Like
0Likes
Like

Posted 04 September 2011 - 06:52 AM

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 parenthesis you mean -

cVector3() : x(0), y(0), z(0) { } // Set all values to zero as default



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?

I've now got rid of the 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.

Thanks for all the advice.
x
"To know the road ahead, ask those coming back."

#9 clb   Members   -  Reputation: 1806

Like
0Likes
Like

Posted 04 September 2011 - 08:02 AM

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.

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.





Me+PC=clb.demon.fi | C++ Math and Geometry library: MathGeoLib, test it live! | C++ Game Networking: kNet | 2D Bin Packing: RectangleBinPack | Use gcc/clang/emcc from VS: vs-tool | Resume+Portfolio | gfxapi, test it live!

#10 Monkan   Members   -  Reputation: 669

Like
0Likes
Like

Posted 04 September 2011 - 09:56 AM

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

#11 japro   Members   -  Reputation: 887

Like
1Likes
Like

Posted 04 September 2011 - 10:14 AM

A good way to figure out how a specific part of codes looks in assembler is to insert "assembler comments" around it.
for gcc this looks like:

asm volatile ("#begin mycode");

//normal c++ stuff

asm volatile ("#end mycode");



you then can just search for "mycode" in the assembler file...

#12 Monkan   Members   -  Reputation: 669

Like
0Likes
Like

Posted 04 September 2011 - 10:30 AM

Cheers, nice little trick, thanks for the advice.

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

#13 iMalc   Crossbones+   -  Reputation: 2338

Like
0Likes
Like

Posted 04 September 2011 - 01:16 PM

I didn't even know that Visual Studio had something to create a class for you. To be honest, it's such a small thing that I would never use it. It probably takes less time to just type it out.

That explains why I've seen (void) from time to time.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms




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