C++ Operator Overloading, and why it blows chunks
first of all i would like to state that i am by no way attacking c++. i use c++ exclusively and have been doing so for at least 5 years now after using turbo pascal, assembler, visual basic for about 5 weeks and turbo basic. i just don''t feel the need for operator overloading and thus i don''t use it. i agree with the above poster about c++ decreasing the coding time with a little decrease in performance. i think to best way to optimize your code is to look at the disassembly and use a profiler which are both included with vc++.
To the vast majority of mankind, nothing is more agreeable than to escape the need for mental exertion... To most people, nothing is more troublesome than the effort of thinking.
Mezz - I'm starting work on my vector class today! I refuse to give up on C++ code!
I think I should set some "rules of engagement":
1. It should be a 3d vector class - three floats. However, how you store these floats is up to you.
2. We're competing on the constructor, copy constructor, assignment operator, and addition and multiplication.
I think perhaps the test program should be the same for both classes, and perform an operation like a = b + c*s, where s is not a vector, but a scalar.
I'd do this, because it's exactly the situation I am in right now.
Perhaps a hard-coded, no function-call version of this should be written as well, so we can compare performance to that. We can do that afterwards though.
Is that enough detail? I'll post my example code up here when I have something working.
#pragma DWIM // Do What I Mean!
~ Mad Keith ~
Edited by - MadKeithV on May 31, 2000 3:39:50 AM
I think I should set some "rules of engagement":
1. It should be a 3d vector class - three floats. However, how you store these floats is up to you.
2. We're competing on the constructor, copy constructor, assignment operator, and addition and multiplication.
I think perhaps the test program should be the same for both classes, and perform an operation like a = b + c*s, where s is not a vector, but a scalar.
I'd do this, because it's exactly the situation I am in right now.
Perhaps a hard-coded, no function-call version of this should be written as well, so we can compare performance to that. We can do that afterwards though.
Is that enough detail? I'll post my example code up here when I have something working.
#pragma DWIM // Do What I Mean!
~ Mad Keith ~
Edited by - MadKeithV on May 31, 2000 3:39:50 AM
It's only funny 'till someone gets hurt.And then it's just hilarious.Unless it's you.
Here's my first attempt, so you guys can try to beat it
Also, some data I gathered from running tests:
( these are just a point sample, don't take them as gospel )
Using assigment operators: 2893 ticks
Assignment separate: 4009 ticks
By "Assignment Separate" I mean that my test code looks like this: result = base + displacement*scale;
The "Assignment Operators" version looks like this:
result=base;
displacement*=scale;
result+=displacement;
[note]: I just profiled my test code, and ran into something interesting. Subsequent testing confirmed: Using the "Assigment Operators" causes all the code to be inline, because the profiler doesn't report a single call to a class function anymore. With the assignment separate, it's calling the * and + functions still. I'll see if I can get more info using disassembly [/note]
#pragma DWIM // Do What I Mean!
~ Mad Keith ~
Edited by - MadKeithV on May 31, 2000 4:14:06 AM
/* JT: CVector3f_1.h : Declaration and implementation of class CVector3f_1. * This is my attempt at creating the fastest, robust 3d vector * class I can muster. * */#if !(defined __CVECTOR3F_1_H)#define __CVECTOR3F_1_Hclass CVector3f_1{public://////////////////////////////////////////////////////////////////////// Constructors, in all shapes and sizes////////////////////////////////////////////////////////////////////// // Default and Initialising constructor CVector3f_1( const float x = 0.0f, const float y = 0.0f, const float z = 0.0f ) : m_x(x), m_y(y), m_z(z) { } // Copy Constructor CVector3f_1( const CVector3f_1 &v ) { *this = v; } // Destructor virtual ~CVector3f_1() { }//////////////////////////////////////////////////////////////////////// Operators////////////////////////////////////////////////////////////////////// // Assignment Operator CVector3f_1& operator=( const CVector3f_1 &v ) { m_x = v.m_x; m_y = v.m_y; m_z = v.m_z; return *this; } // Addition Operator CVector3f_1 operator+( const CVector3f_1 &v ) { return CVector3f_1( m_x + v.m_x, m_y + v.m_y, m_z + v.m_z ); } // Scalar Multiplication Operator CVector3f_1 operator*( const float s ) { return CVector3f_1( s * m_x, s * m_y, s * m_z ); } // Addition and Assignment CVector3f_1& operator+=( const CVector3f_1 &v ) { m_x += v.m_x; m_y += v.m_y; m_z += v.m_z; return *this; } // Scalar Multiplication and Assignment CVector3f_1& operator*=( const float s ) { m_x *= s; m_y *= s; m_z *= s; return *this; }protected:private: float m_x, m_y, m_z;};#endif //__CVECTOR3F_1_H
Also, some data I gathered from running tests:
( these are just a point sample, don't take them as gospel )
Using assigment operators: 2893 ticks
Assignment separate: 4009 ticks
By "Assignment Separate" I mean that my test code looks like this: result = base + displacement*scale;
The "Assignment Operators" version looks like this:
result=base;
displacement*=scale;
result+=displacement;
[note]: I just profiled my test code, and ran into something interesting. Subsequent testing confirmed: Using the "Assigment Operators" causes all the code to be inline, because the profiler doesn't report a single call to a class function anymore. With the assignment separate, it's calling the * and + functions still. I'll see if I can get more info using disassembly [/note]
#pragma DWIM // Do What I Mean!
~ Mad Keith ~
Edited by - MadKeithV on May 31, 2000 4:14:06 AM
It's only funny 'till someone gets hurt.And then it's just hilarious.Unless it's you.
May 31, 2000 04:36 AM
Hi
Just stopped by and noticed your discussion... There is a way to make operator overloading faster. You need to introduce an auxiliary class:
struct Vsum {
const Vector &v
const Vector &w
Vsum(const Vector &vv, const Vector &ww) : v(vv), w(ww) { }
operator Vector(); // evaluate and return result
};
inline Vsum operator+(const Vector &vv, const Vector &ww)
{
return Vsum(vv, ww);
}
Thus adding two vectors merely stores references to its operands; the addition is deferred. Vector would be defined like this:
class Vector {
// ...
public:
Vector(const Vsum &s) { // initialize by Vsum
// do whatever construction is needed
add_and_assign(this, &s.v, &s.w);
}
Vector& operator=(const Vsum &s) {
add_and_assign(this, &s.v, &s.w);
return *this;
}
// ...
};
You define the add_and_assign(Vector *x, Vector *y, Vector *z) function to do *x = *y + *z (with no temporaries needed).
So x = y + z is expanded to
x.operator=(Vsum(y,z))
which because of inlining becomes
add_and_assign(&x, &y, &z)
This means you can make operator overloading as fast as a direct function call. Effectively we are treating x = y + z as a single operator with three operands. You''d have to set this up for all the combinations of operators you want to use though so it can get quite complicated to program (depends how much you like the overloaded operators...)
By the way I haven''t implemented any of the above so I don''t know how much faster it is, but given a good optimizer it should work well. The technique is mentioned in Stroustrup''s "The C++ Programming Language" (3rd ed., p. 675).
Just stopped by and noticed your discussion... There is a way to make operator overloading faster. You need to introduce an auxiliary class:
struct Vsum {
const Vector &v
const Vector &w
Vsum(const Vector &vv, const Vector &ww) : v(vv), w(ww) { }
operator Vector(); // evaluate and return result
};
inline Vsum operator+(const Vector &vv, const Vector &ww)
{
return Vsum(vv, ww);
}
Thus adding two vectors merely stores references to its operands; the addition is deferred. Vector would be defined like this:
class Vector {
// ...
public:
Vector(const Vsum &s) { // initialize by Vsum
// do whatever construction is needed
add_and_assign(this, &s.v, &s.w);
}
Vector& operator=(const Vsum &s) {
add_and_assign(this, &s.v, &s.w);
return *this;
}
// ...
};
You define the add_and_assign(Vector *x, Vector *y, Vector *z) function to do *x = *y + *z (with no temporaries needed).
So x = y + z is expanded to
x.operator=(Vsum(y,z))
which because of inlining becomes
add_and_assign(&x, &y, &z)
This means you can make operator overloading as fast as a direct function call. Effectively we are treating x = y + z as a single operator with three operands. You''d have to set this up for all the combinations of operators you want to use though so it can get quite complicated to program (depends how much you like the overloaded operators...)
By the way I haven''t implemented any of the above so I don''t know how much faster it is, but given a good optimizer it should work well. The technique is mentioned in Stroustrup''s "The C++ Programming Language" (3rd ed., p. 675).
Interesting! I might try that later.
#pragma DWIM // Do What I Mean!
~ Mad Keith ~
#pragma DWIM // Do What I Mean!
~ Mad Keith ~
It's only funny 'till someone gets hurt.And then it's just hilarious.Unless it's you.
I want to comment on some methods :-)
// Addition Operator CVector3f_1 operator+( const CVector3f_1 &v ) { return CVector3f_1( m_x + v.m_x, m_y + v.m_y, m_z + v.m_z ); }
change this definition into:
const CVector3f_1 operator+(const CVector3f_1&) const;
Keep everything else. The const at the return parameter will secure you from things like: v1 + v2 = v3
This is unlikely to happen but the primitve c++ types disallow such statements.
The const at the end of the declaration will allow you to use the function even with const vectors. You don''t change any values of the instance for which you call operator+ so you can use the const. This is the same for operator*(), operator-() and operator/().
One comment more: for such a basic class like CVector3f_1 you should make up your mind if you want to allow inheritance. The use of the virtual destructor will incorporate a virtual function table which will add 4 bytes to the memory consumption of your class instances. This could be good (natural memory alignment) however in the most cases you want to decide yourself about the memory consumption and allignment.
I hope this was understandable???
Bjoern
// Addition Operator CVector3f_1 operator+( const CVector3f_1 &v ) { return CVector3f_1( m_x + v.m_x, m_y + v.m_y, m_z + v.m_z ); }
change this definition into:
const CVector3f_1 operator+(const CVector3f_1&) const;
Keep everything else. The const at the return parameter will secure you from things like: v1 + v2 = v3
This is unlikely to happen but the primitve c++ types disallow such statements.
The const at the end of the declaration will allow you to use the function even with const vectors. You don''t change any values of the instance for which you call operator+ so you can use the const. This is the same for operator*(), operator-() and operator/().
One comment more: for such a basic class like CVector3f_1 you should make up your mind if you want to allow inheritance. The use of the virtual destructor will incorporate a virtual function table which will add 4 bytes to the memory consumption of your class instances. This could be good (natural memory alignment) however in the most cases you want to decide yourself about the memory consumption and allignment.
I hope this was understandable???
Bjoern
Good comments - the "const" at the end I've already added ( I'm using const vectors a lot. )
I'm removing the virtual part of the destructor now, and adding the leading consts too.
[addition]
Hmm, that actually improved performance! I guess those 4 bytes more made a difference . It's not a big change, but it's a lot more consistent now.
( CPP-style now about 100% slower than C-style, a big improvement over earlier ).
[/addition]
#pragma DWIM // Do What I Mean!
~ Mad Keith ~
Edited by - MadKeithV on May 31, 2000 6:14:06 AM
I'm removing the virtual part of the destructor now, and adding the leading consts too.
[addition]
Hmm, that actually improved performance! I guess those 4 bytes more made a difference . It's not a big change, but it's a lot more consistent now.
( CPP-style now about 100% slower than C-style, a big improvement over earlier ).
[/addition]
#pragma DWIM // Do What I Mean!
~ Mad Keith ~
Edited by - MadKeithV on May 31, 2000 6:14:06 AM
It's only funny 'till someone gets hurt.And then it's just hilarious.Unless it's you.
Do you have a standard sorta test-bed I can run my code through? To profile it on, etc...
(Will post source later hopefully, if it tests OK)
-Mezz
(Will post source later hopefully, if it tests OK)
-Mezz
Another thing you might try, is remove the destructor completely -- there''s no need for the dtor (except for maybe triggering the "rule of the big three(*)" ... but...) there''s no need for a copy ctor, the compiler generated one will probably be as fast, and will likely be generated inline.
Another thing to think about: think about the operations
that will generatlly be performed on the vector. if you''re
doing a dot product, write a member function to do it and manipulate the members directly instead of relying on the overridden operators; it''s the level of encapsulation that matters: think about the class from the USER''s perspective, not the programmers perspective. If users are generally NOT going to be mulitplying vectors, but only as a side effect of doing another vector operation, then encapsulate that vector operation and optimize it -- the clue is, avoid "context switches" into and out of the class -- you can even drop to ASM in the class implementation, and not effect the interface.
-- Pryankster
* Rule of the big three: from the book "C++ FAQs" by Cline, M, et. al. chapter 30; the big three are the dtor, copy ctor , and assignment. they are needed when the contents of an object need special care during assignment, destruction, or copying -- for example, when transferring ownership of a pointer, etc. The Vector class does not need any of these, because it''s contents are simple.
Another thing to think about: think about the operations
that will generatlly be performed on the vector. if you''re
doing a dot product, write a member function to do it and manipulate the members directly instead of relying on the overridden operators; it''s the level of encapsulation that matters: think about the class from the USER''s perspective, not the programmers perspective. If users are generally NOT going to be mulitplying vectors, but only as a side effect of doing another vector operation, then encapsulate that vector operation and optimize it -- the clue is, avoid "context switches" into and out of the class -- you can even drop to ASM in the class implementation, and not effect the interface.
-- Pryankster
* Rule of the big three: from the book "C++ FAQs" by Cline, M, et. al. chapter 30; the big three are the dtor, copy ctor , and assignment. they are needed when the contents of an object need special care during assignment, destruction, or copying -- for example, when transferring ownership of a pointer, etc. The Vector class does not need any of these, because it''s contents are simple.
-- Pryankster(Check out my game, a work in progress: DigiBot)
Mezz - I do have a testbed, but it''s at work, and I''m off for the next week ;-)
Just write your own test-bed and try it out, I like the comments in here so far!
About the destructor - I WILL do that on tuesday and see how much difference it makes.
Encapsulation: Yes, I was going there next.
( Implementing the offending operation as a single member function )
In fact, I was also going to make my vector class VERY different, using a static member containing a pre-allocated block of memory, to make the copying and constructing much faster (just a question of returning the next free index )
#pragma DWIM // Do What I Mean!
~ Mad Keith ~
Just write your own test-bed and try it out, I like the comments in here so far!
About the destructor - I WILL do that on tuesday and see how much difference it makes.
Encapsulation: Yes, I was going there next.
( Implementing the offending operation as a single member function )
In fact, I was also going to make my vector class VERY different, using a static member containing a pre-allocated block of memory, to make the copying and constructing much faster (just a question of returning the next free index )
#pragma DWIM // Do What I Mean!
~ Mad Keith ~
It's only funny 'till someone gets hurt.And then it's just hilarious.Unless it's you.
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement
Recommended Tutorials
Advertisement