PDA

View Full Version : Should i use a c++ Vector class?



Junkstyle
07-01-2002, 11:02 PM
What do most people use in their OGL programs for vector operations? Do they use a c++ class with operator overloads or something else? I'm wondering if there are any downsides in using a c++ vector class for doing misc vector calculations in an OGL program. Is this the "wrong" route or the "correct" route?

[This message has been edited by Junkstyle (edited 07-02-2002).]

mikael_aronsson
07-01-2002, 11:32 PM
Hi !

Using a C++ vector class makes life pretty simple for you, the downside is that the code generated is normally pretty slow compared to if you do the same thing with C macros or inline functions, but if you are a bit careful when you create the vector class you should be able to get ok performance, the most common problem is that the operator overloading generates loads of temporary objects on the stack.

For my own use I have a vector class with all the basic stuff, but I also have a set of inline functions to do most common operations (dot/cross prod add mul and so on).

Mikael

JML
07-01-2002, 11:42 PM
In addition to Mikaels advice, you have to think about how your vector class is going to interact with OpenGL.
Using VAR for example puts restrictions on the way the vector data is to be stored before feeding it to OpenGL (arrays of vertices/texcoords as opposed to individual vectors).

HTH

Jean-Marc

Bob
07-02-2002, 02:26 AM
With proper design and usage, and a good compiler, the performance hit compared to macros can be close to zero. And don't forget the fact that it can be more type safe than macros too.

Myself, I use a general matrix class for both matrices and vectors (a vector is a special case of a matrix), letting me define vectors and matrices of arbitrary size and datatype.

Robbo
07-02-2002, 02:31 AM
....actually you can inline your class methods. So using a vector class is probably best.

coredump
07-02-2002, 05:35 AM
i also am on the Vector class bandwagon. careful when using sizeof() with your vector class. if you have any virtual functions, your classes size will increase by 4 (on windows). apparently any virtual functions are stored in a separate table and the 4 bytes are the starting location of the table in memory.

b

Robbo
07-02-2002, 10:32 AM
Thats right coredump - I was going to say that v functions with such a basic type should be avoided - not only for those practical reasons, but also because it will run like a dog.

Each method in a vector class will be relatively trivial to implement in any case.

Miguel_dup1
07-02-2002, 01:15 PM
I used vectors very often and even used them in my engine to store vertices and all kinds of stuff... I saw using it in the torque engine... Now with Vsiual Studio 6 I had no problem, they worked fine...
I switched over to Visual Studio .NET and the performance went to the mighty hell... I ran a debugger through it and vectors had a huge overhead. I changed my entire code to using arrays and everything worked fine then.

I would not use them and do not recommend them.

coredump
07-02-2002, 06:43 PM
mancha - you are thinking of stl vectors. junkstyle was referring to a vector representing 3 points in space.

Junkstyle
07-02-2002, 09:49 PM
Thanks for all the input everyone. Seems like using a simple C++ class is the way to go.

To clarify a bit I was asking about if it was an okay design to use a C++ class for vectors and matrix storage and calculations. They would be used for things like calculating camera panning, physics, just about anything to do with vectors.

I don't think I would store vertex data in them for things like models. I'm thinking I'd just using arrays for that.

So basically my question is should I use:
a) C++ class with operator overloading

or

b) simple 3 dimension array with function calls, i.e. Normalize(), Add(), Dot(), Cross() to do the operations

mikael_aronsson
07-02-2002, 10:50 PM
Hi !

Use a vector/matrix class, but avoid operator overloading, that's what kills performance on some compilers.

Mikael

Robbo
07-03-2002, 03:25 AM
Operator overloading is ok in situations where you are doing this:

myvec += myothervec;

No performance hit there. When you are doing this:

myvec = ( myfirstvec + mysecondvec), you have to create and return a temporary vector object, which may hurt a little.

As usual, its often easier to use operator overloading in situations where you need to write a formula and have it look right. For example:

mylen = ( v0 + ( ( v3 - v1 ) ^ v2 ) ).Length ();

With the above, if you want to code it any other way you will have to use temporaries anyway. So you might as well go the whole hog and use operator overloading.

Ok, optimize your algorithms AND THEN micro-optimise your code if you see that the overloading is actually the cause of a performance prob.

Junkstyle
07-03-2002, 01:25 PM
Well after thinking for a bit about this I've decided to go with the minimalist approach and just do:

typedef float Vector3[3];

for my vector type and use function calls for things like dot products, cross products, normalize, add, subtract, etc, etc.

Now I may regret this a month from now but I'll let you guys know how it goes. I'm sure the code, especially the "formulas" for doing vector calculations are going to look like a bunch of function calls instead of something thats easier to read but I think the tradeoff is that it will be easier to pass around an array[3] to functions around my program and to opengl.

Thanks for all the input and I'm sure I won't know how good or bad this idea is until I actually write a few thousand lines of code using it.

ioquan
07-06-2002, 11:48 PM
I use a class called Point3D for storing both vectors and points. Points and vectors are both represented the same way, and many of the same operations can be done on them, so it makes sense to use one class for both, even though they are technically two different things.

I highly recommend using a point class, and giving it only 3 data members, with no virtual functions. By doing this, the class will line up in memory just as a float[3] or double[3] would. This is important because you can then send your vertices and normals to OpenGL with an array of Point3D's (or whatever you call your vector/point class)!

Point3D normals [100];
Point3D verts [100];
...
glNormalPointer(GL_FLOAT,0,normals);
glVertexPointer(3,GL_FLOAT, 0, verts);

Miguel_dup1
07-07-2002, 05:38 AM
That is how I use it JunkStyle...
Do not make your code heavier than it should be.


Originally posted by Junkstyle:
Well after thinking for a bit about this I've decided to go with the minimalist approach and just do:

typedef float Vector3[3];

for my vector type and use function calls for things like dot products, cross products, normalize, add, subtract, etc, etc.

Now I may regret this a month from now but I'll let you guys know how it goes. I'm sure the code, especially the "formulas" for doing vector calculations are going to look like a bunch of function calls instead of something thats easier to read but I think the tradeoff is that it will be easier to pass around an array[3] to functions around my program and to opengl.

Thanks for all the input and I'm sure I won't know how good or bad this idea is until I actually write a few thousand lines of code using it.

Junkstyle
07-08-2002, 09:33 AM
I've been using a typedef vector and function calls lately. I'm finding I do have to use a lot of temp variables. Looking at some vector class code I noticed that the member functions aren't declared as inline. Any reason for this? They should be inline shouldn't they?

Anyways I'm not going to get back to this vector class until I'm done wrestling with these object rotations.

Yes my spaceship will fly...its only a matter of time.

coredump
07-08-2002, 10:07 AM
Looking at some vector class code I noticed that the member functions aren't declared as inline. Any reason for this? They should be inline shouldn't they?
i'm not sure what code you saw, but any member function defined in the class declaration are implicity inline. example:



class MyClass
{
public:
MyClass();

void print() // this is inline
{
cerr << data << endl;
}

private:
char *data;
};


b

velco
07-08-2002, 09:49 PM
Originally posted by Junkstyle:
Well after thinking for a bit about this I've decided to go with the minimalist approach and just do:

typedef float Vector3[3];

for my vector type and use function calls for things like dot products, cross products, normalize, add, subtract, etc, etc.


I use the same in my proggies.

The advantage over C++ and operator overloading is that I can have functions like




void cross_product (Vector3 a[restrict], const Vector3 b[restrict]);

which is equivalent to "a += b" (no diff to C++),

and




void cross_product_new (Vector3 a[restrict], const Vector3 b[restrict], const Vector3 c[restrict]);


which is equivalent to "a = b + c", but WITHOUT temporaries or extra constructor invocations.

~velco

ScottManDeath
07-09-2002, 03:39 AM
Hi

I would use a vector class with operator overloading like += ... +/- /* but for often uses calculations I would write some inlined functions wich take vector classes as arguments and minimize using of temporaries.

for example

float s;
vec3 a;
vec3 b;
vec3 c;

c= a*s+(1-s)*b; // linear interpolation


would be

vec3 a,b,c;
float s;

c=a;
lerp(a,b,s);

inline void lerp(vec3& a, const vec3& b, float s)
{
a*=s;
vec3 tmp=b*(1-s);
a+=tmp;
}
Maybe the example is not the best but you should see how it should work;

This will get effective when more objects are involved (matrixes...)

Ideally you will have complete set of functions that do all operations on float[3]and then you will have classe that call these functions for convenience. With functions you can easily implement SSE or 3DNow and recompile the class without any problems


Bye
ScottManDeath

Jambolo
07-09-2002, 06:19 PM
Originally posted by velco:



void cross_product_new (Vector3 a[restrict], const Vector3 b[restrict], const Vector3 c[restrict]);


which is equivalent to "a = b + c", but WITHOUT temporaries or extra constructor invocations.

~velco


Just make sure you don't do this accidently or indirectly:

cross_product_new( a, a, b );

velco
07-09-2002, 11:05 PM
Originally posted by Jambolo:



void cross_product_new (Vector3 a[restrict], const Vector3 b[restrict], const Vector3 c[restrict]);


which is equivalent to "a = b + c", but WITHOUT temporaries or extra constructor invocations.

~velco<HR></blockquote>


Just make sure you don't do this accidently or indirectly:

cross_product_new( a, a, b );

[/B]

Sure, you gotta be careful http://www.opengl.org/discussion_boards/ubb/smile.gif. TANSTAAFL.

~velco

Jambolo
07-12-2002, 08:56 PM
void cross_product_new (Vector3 a[restrict], const Vector3 b[restrict], const Vector3 c[restrict]);

which is equivalent to "a = b + c", but WITHOUT temporaries or extra constructor invocations.
~velco

Sometimes, its faster to use temporaries. When there is no guarantee that memory being written is different from memory being read, the compiler must read/write every value from/to memory. With temporaries, frequently used values can be cached in registers.

For example, bar() is much faster than foo().






void foo( float *a, float *m, float *b )
{
a[0] = m[0]*b[0] + m[1]*b[1];
a[1] = m[2]*b[0] + m[3]*b[1];
}
void bar( float *a, float *m, float *b )
{
float x[2];
x[0] = m[0]*b[0] + m[1]*b[1];
x[1] = m[2]*b[0] + m[3]*b[1];
a[0] = x[0]; a[1] = x[1];
}


foo() generates 10 memory accesses and bar() generates only 8. This is because the compiler must worry that a[] overlaps m[] and b[] in foo(), but not in bar(). In bar(), x[0], x[1], b[0], and b[1] can be cached in registers.

In addition, bar() is more suited to parallel execution!


[This message has been edited by Jambolo (edited 07-12-2002).]

Junkstyle
07-12-2002, 11:16 PM
Which version of this vector function would be faster, or would it be the same speed?

inline void VectorAdd(vector3 a, vector3 b, vector3 result)
{
result[0] = a[0] + b[0];
result[1] = a[1] + b[1];
result[2] = a[2] + b[2];
}

inline void VectorAdd(const vector3 a, const vector3 b, vector3 result)
{
result[0] = a[0] + b[0];
result[1] = a[1] + b[1];
result[2] = a[2] + b[2];
}


Note: I doubt any of this is going to make any measurable performance difference, but I'm interested in the academics of it.

[This message has been edited by Junkstyle (edited 07-13-2002).]

Citizen Thomas
07-13-2002, 12:14 AM
There's no difference in speed. Enforcement of const restraints is performed by the compiler at compile time, and has nothing to do with what gets done at execution time. Thus, the code that actually gets executed will be identical between the two versions, and there'll be no speed difference.

velco
07-13-2002, 03:13 AM
void cross_product_new (Vector3 a[restrict], const Vector3 b[restrict], const Vector3 c[restrict]);

which is equivalent to "a = b + c", but WITHOUT temporaries or extra constructor invocations.
~velco


Sometimes, its faster to use temporaries. When there is no guarantee that memory being written is different from memory being read, the compiler must read/write every value from/to memory. With temporaries, frequently used values can be cached in registers.


Yes, that's why I explicitly specified that the three arrays may not overlap.

For the case where the one of sources and the destination overlap, one can use the first function.

(FWIW, a third function is needed, when the result overlaps with the other parameter, as the cross product is not cimmutative http://www.opengl.org/discussion_boards/ubb/wink.gif )

~velco


[This message has been edited by velco (edited 07-13-2002).]

velco
07-13-2002, 03:26 AM
Originally posted by Citizen Thomas:
There's no difference in speed. Enforcement of const restraints is performed by the compiler at compile time, and has nothing to do with what gets done at execution time.

While enforcement of the const-ness is indeed performed in compile time, it surely affects the code generation, probably not in these small functions, but where they are expanded.

~velco

harsman
07-13-2002, 03:56 AM
Constant optimization? (http://www.gotw.ca/gotw/081.htm)

Not really, no ;-) Or rather, sometimes, but not very often.

Junkstyle
07-13-2002, 09:04 AM
So I should go with the non-const version of VectorAdd() then?
It would allow me do a = a + b with one function call:

VectorAdd(a, b, a);

instead of having to do:

vector3 temp;
VectorAdd(a, b, temp);
VectorCopy(a, temp);

And don't worry I use const for my VectorCross() function--thanks for looking out for me though.

One interesting point is John Carmack uses the VectorCopy version for all his vector functions like this. I'm lazy and don't want to type an extra function call in all my formulas using vectors. The only thing I see the const version has going for it is the const correctness, i.e. if you have a const correct function that calls VectorAdd you wouldn't have to call VectorCopy to make a copy of a const variable to input it into VectorAdd.

[This message has been edited by Junkstyle (edited 07-13-2002).]

Mezz
07-13-2002, 12:37 PM
Consider this:

class Point // or Vector, or whatever.
{
float xyz[3];
// other stuff
};

I find this a good approach because you can pass around your array to the vector forms of OpenGL commands e.g.

glVertex3fv(mypoint.xyz);

You can also use operator overloading, if it suits you. If not, then you can just use regular C functions like VectorAdd/Cross/Subract etc.

I find that it's easier to deal with than straight float arrays.

-Mezz

velco
07-13-2002, 12:56 PM
Originally posted by harsman:
Constant optimization? (http://www.gotw.ca/gotw/081.htm)

Not really, no ;-) Or rather, sometimes, but not very often.

Not true. All this BS is based on the
assumption that the compiler has no aliasing
information. Compilers usualy do pointer
alias analysis. And, of course, there's the
"restrict" keyword.

This one was particularly funny:
"If you're getting true global optimization
anyway, then promising constness definitely
doesn't help the optimizer do its job any
better in this respect."

Just the opposite, global optimizations result in better aliasing information.

~velco

Junkstyle
07-13-2002, 07:28 PM
Mezz, I got an even better class setup for you:

class Vector3{
public:
union
{
float data[3];
struct
{
float x, y, z;
};
};
};

With that you can access your vector data either:

foo.x

or

foo.data[0]

I'm still writing my game demo with functions right now though cause I don't want to redo it all but vector classes seem pretty cool to me.

harsman
07-14-2002, 02:23 AM
Velco, I don't think you understood Herb Sutter's point. Usually, you compile one source file at a time and therefore the compiler has no knowledge of if a value that is const in the current translation unit is non-const somewhere else. Therefore the const won't help much. And if the compiler does global optimzation, then it has aliasing information *anyway* so the const won't make much of a difference either. That doesn't mean const isn't a good thing, being const correct is good. And if you declare a variable const from the start, then it probably enables optimization oppourtunities since the compiler can be certain it's read only (barring const_cast<> of course).

velco
07-14-2002, 07:04 AM
Originally posted by harsman:
Velco, I don't think you understood Herb Sutter's point. Usually, you compile one source file at a time and therefore the compiler has no knowledge of if a value that is const in the current translation unit is non-const somewhere else.


Indeed. Howver global optimization means across function calls as opposed to one function at a time. And I have worked with at least two compilers, which can optimize the whole program, before linking. (of course, they re-generated the code from intermediate representation files, AFAICT).



Therefore the const won't help much. And if the compiler does global optimzation, then it has aliasing information *anyway* so the const won't make much of a difference either.
[B]

It ain't so. One needs both pieces of information. A trivial example:




void foo(const float *);

a = b * c[i];
foo (c);
d = e * c[i];


Assuming, c is no aliased, the compiler can save assume that the value of c[i] is the same before and after the call, thus avoiding a second load by possibly using the register where it loaded c[i] the first time (will, "i" does not change too.

One can imagine the same for several elements of c, i.e., for a whole vector.

Also, the "non-changing" property may propagate further on. Some of the expressions (e.g. c[i]) may appear to be loop invariants, thus moved to the prologue. If the compiler can infer "b == e" the above expression become common subexpression and can be eliminated by doing "d = a". Moreover, this assignment may be moved before the call, if this would result in better instruction scheduling. Etc., etc.

My point is, while "const" by itself is not of much use for an optimizer, it does not stand alone. Coupled with aliasing information is can be useful for almost all optimization phases, by enabling the optimizer to obtain more accurate solutions to the data-flow equations.


[B]
That doesn't mean const isn't a good thing, being const correct is good. And if you declare a variable const from the start, then it probably enables optimization oppourtunities since the compiler can be certain it's read only (barring const_cast<> of course).

See, I understand what he said. All of it is true, under some assumptions. I claim that [i]the assumptions/[i] are false.

As for his point, indeed, I do not understand his point. Under some cirmustances "const" won't give any advantage to the optmization phases? So, what? We wan't use "const" for this reason? I don't think so. Then what's the purpose of the article, other than to make him feel good http://www.opengl.org/discussion_boards/ubb/smile.gif

~velco

harsman
07-15-2002, 02:47 AM
My point was just that const probably won't help optimization as much as you might think at first. It's still good, but not a magic cure-all for optimization. Bah, I'm probably just generating confusion in the beginners forum :-) Use const kids! It's good for ya. I'll go away now and stop nitpicking :-)

Junkstyle
07-15-2002, 01:52 PM
Remember this question?

So I should go with the non-const version of VectorAdd() then?
It would allow me do a = a + b with one function call:

VectorAdd(a, b, a);

instead of having to do:

vector3 result;
VectorAdd(a, b, result);
VectorCopy(a, result);

I suggest using the const version.
Who cares about the extra work to make a temp value to hold the result. It just majorly sucks not having const correct fuctions. I've been taking the short cut and got up to my neck in problems and paranoia with not having it const correct. I'm going back and changing mine back to the const version.

Score 1 more point for the Vector Class folks.

[This message has been edited by Junkstyle (edited 07-15-2002).]

Junkstyle
07-15-2002, 02:11 PM
Actually I changed it all to const correct and it really doesn't matter I found. My vector is in this format:

typedef float vector3[3];

so passing in a const array to a function doesn't stop you from dereferencing it and assigning different values to the array.

harsman
07-15-2002, 09:15 PM
There's a difference between a const pointer and a pointer to a constant object.




const float* foo; //is a pointer to a const float
float* const bar; //is a const pointer to a float
const float* const spam; //is a const pointer to a const float


My guess is you've made the pointer to the array const but not the values. Try here (http://www.parashift.com/c++-faq-lite/const-correctness.html) for further clarification. Sorry if I confused you, being const correct is always good.

velco
07-15-2002, 09:43 PM
Originally posted by Junkstyle:
Remember this question?

So I should go with the non-const version of VectorAdd() then?
It would allow me do a = a + b with one function call:

VectorAdd(a, b, a);

instead of having to do:

vector3 result;
VectorAdd(a, b, result);
VectorCopy(a, result);


Have both, for the cases where you don't want to clobber one of the operands.

~velco