Slow Class Operator Overloading?

Started by
7 comments, last by WhatEver 18 years, 7 months ago
This topic popped up in my other thread so I thought I'd make a new thread discussing it since speed was questioned. According to my benchmarks, my operator overloading vector class is a lot slower than my straight C vector functions (this time I conducted the test in release mode). It's possible that my operator overloading isn't set up right, so could you look at it for me? Named txt so it will open in your broweser. Header file: http://www.spider3d.com/shared/s3d_vector.txt The classes are at the top, the straight C functions follow after the class, S3Dvec3f is just a typedef (#typedef float S3Dvec3f[3]). Example: //======== //Straight C using functions //======== S3Dvec3f CV1, CV2; S3Dfloat DOT; //...set up vectors DOT = s3dVecDot3f(CV1, CV2); //======== //Operator Overloading using classes //======== s3d_vec OOV1, OOV2; //...set up vectors DOT = OOV1 * OOV2;
Advertisement
try this

S3Dscalar operator*(const s3d_vec3& v)const{	return x*v.x + y*v.y + z*v.z;}


avoid passing by value if the functions are so short (and use const! :) )

EDIT: I've not seen the link to the code; now i've posted this hint
The first thing I notice is that your functions are taking the vector arguments by value. This is not happy. Change the arguments to take the vectors by const reference. The second thing I notice is that you're overloading operator*() to do the dot-product. This can be non-intuitive. From a usability standpoint, I would consider eliminating this overload and just using your non-member function instead.
As is usually the case in these situations you are creating unnecessary temporary objects. You should actually prefer to write your operators as non-member (possibly friend) functions and implement operatorX in terms of operatorX=, i.e.:
class SomeClass{	public:		SomeClass(float c, float d);		float a;		float b;};// we just modify an existing SomeClass objects, so pass and return by reference// and avoid creating any temporariesSomeClass & operator+=(SomeClass & lhs, SomeClass const & rhs){	lhs.a += rhs.a;	lhs.b += rhs.b;	return lhs;}// since we return a SomeClass by value we need to construct one in the function// we can take one parameter by value to automatically construct a new instanceSomeClass operator+(SomeClass lhs, SomeClass const & rhs){	return (lhs += rhs);}

But also take SiCrane's advice about operator* calculating the dot-product. It fails the test of least astonishment.

Enigma
I will try your suggestions later today and benchmark the result.

Yeah, I thought the '*' = DOT was unintuitive too. The '*' had to be used for either the DOT product or the cross product, so I eventualy opted for the DOT product simply because I didn't want to waste the use of the operator, although neither really seamed intuitive.
The results were close. With the optimizations suggested (and in release mode), straight C won, but by a very small margin. Out of 10000000 calculations per method, straight C scored 65 milliseconds, and operator overloading scored 69 milliseconds. The results were based on an average of 32 tests.

The source code:
	s3d_vec3 OOV1, OOV2, OOV3;	S3Dvec3f	CV1, CV2, CV3;	s3dVecSet3f(CV1, 0.0f, 1.0f, 0.0f);	s3dVecSet3f(CV2, 0.0f, 0.0f, 1.0f);	OOV1.Set(0.0f, 1.0f, 0.0f);	OOV2.Set(0.0f, 0.0f, 1.0f);	S3Duint	tick;	S3Duint	milli1=0, milli2=0;	S3Dfloat DOT;	S3Dchar string[128];	S3Duint j;	for(int i=0; i<32; i++)	{		tick=timeGetTime();		for(j=0; j<10000000; j++)		{			//s3dVecScale3f(CV2, CV1, 10.0f);			DOT=s3dVecDot3f(CV1, CV2);			//s3dVecCross3f(CV3, CV1, CV2);			//s3dVecNormalize3f(CV1);		}		milli1+=timeGetTime()-tick;		tick=timeGetTime();		for(j=0; j<10000000; j++)		{			DOT=OOV1*OOV2;			//OOV3.Cross(OOV1, OOV2);			//s3dVecNormalize3f2(CV1);			//OOV1=1.0f;		}		milli2+=timeGetTime()-tick;	}	itoa(milli1/32, string, 10);	MessageBox(NULL, string, "Straight C", MB_OK);	itoa(milli2/32, string, 10);	MessageBox(NULL, string, "Operator Overloading", MB_OK);


The other vector routine results proved to come in just as close, but C always won. Using C over operator overloading is pretty much a toss up now. I could stick with C and be happy, or I could start using operator overloading and still be happy.

Thanks again for all your help!
"Yeah, I thought the '*' = DOT was unintuitive too."

Then do not do it

"The '*' had to be used for either the DOT product or the cross product,"

No it didn't. It doesn't have to be used at all.

"so I eventualy opted for the DOT product simply because I didn't want to waste the use of the operator,"

AAAAAHHHHHHHHHHHH AAAHHHHHHHHHH my brain exploded
No operator is ever "wasted". Operators should only be used when they make total and complete sense. Using them because you think that you have to, or that you will be wasting them if you don't use them, just gives operator overloading a bad name and turns people against the concept.

"although neither really seamed intuitive."

A strong argument for not doing it.

Sometimes overloading operators is good. Other times it is bad. I could use / to tokenize strings but I don't, because doing so would be insane. I mean it actually makes sense kind of, what if I did this:

string s("hi I am a string");
string* sa = s/" "; //now sa points to an array of strings "divided" by " ".

Now see to some that makes sense, and there is a rational behind it, but it is crazy. Don't go down the slippery slope into cryptic nonsense code. Operator overloading exists to make code clearer, not to save typing.
Quote:Original post by WhatEver
The results were close. With the optimizations suggested (and in release mode), straight C won, but by a very small margin. Out of 10000000 calculations per method, straight C scored 65 milliseconds, and operator overloading scored 69 milliseconds. The results were based on an average of 32 tests.

The source code:
*** Source Snippet Removed ***

The other vector routine results proved to come in just as close, but C always won. Using C over operator overloading is pretty much a toss up now. I could stick with C and be happy, or I could start using operator overloading and still be happy.

Thanks again for all your help!

You shouldn't even be seeing that much difference. With the source code:
#include <ctime>#include <iostream>class s3d_vec{	public:		void Set(float x_, float y_, float z_)		{			x = x_;			y = y_;			z = z_;		}		float x;		float y;		float z;};inline float operator*(s3d_vec const & lhs, s3d_vec const & rhs){	return (lhs.x * rhs.x) + (lhs.y * rhs.y) + (lhs.z * rhs.z);}typedef float S3Dvec3f[3];inline void s3dVecSet3f(S3Dvec3f vec, float x, float y, float z){	vec[0] = x;	vec[1] = y;	vec[2] = z;}inline float s3dVecDot3f(S3Dvec3f lhs, S3Dvec3f rhs){	return (lhs[0] * rhs[0]) + (lhs[1] * rhs[1]) + (lhs[2] * rhs[2]);}int main(){	s3d_vec oov1;	s3d_vec oov2;	oov1.Set(0, 1, 0);	oov2.Set(0, 0, 1);	S3Dvec3f cv1;	S3Dvec3f cv2;	s3dVecSet3f(cv1, 0, 1, 0);	s3dVecSet3f(cv2, 0, 0, 1);	float dotAccumulator = 0;	float times[2];	times[0] = std::clock();#if defined(CPP)	for (int i = 0; i < 1000000000; ++i)	{		dotAccumulator += oov1 * oov2;		oov1.Set(dotAccumulator, 1, 0);	}#else	for (int i = 0; i < 1000000000; ++i)	{		dotAccumulator += s3dVecDot3f(cv1, cv2);		s3dVecSet3f(cv1, dotAccumulator, 1, 0);	}#endif	times[1] = std::clock();#if defined(CPP)	std::cout << "OOP: " << (times[1] - times[0]) << '\n';#else	std::cout << "C:   " << (times[1] - times[0]) << '\n';#endif	return dotAccumulator;}

I get the following results:
Borland:
OOP: 23469
C: 23469

Gcc:
OOP: 13843
C: 13859

Visual C++:
OOP: 4250
C: 4265
The small differences there are almost certainly down to the precision of the timer.

Enigma
Glak, I know you're right. I was just so excited to finaly use operater overloading I didn't care how it was implimented, I just wanted to see if I could get it to work. It was my first time implimenting it, so it was mostly an experiment.

Enigma, you have quite an arsonal there! I would agree with your results, but when I conducted the test, the C versus CPP versions were varying dramaticly. So I figured it was the OS doing its preemtive multitasking. So I decided to work with an average instead of one cycle to allow both methods to share any lag from the OS. Let me know what you think.

The different compilers results are insain!...and I can't spell!!!

This topic is closed to new replies.

Advertisement