george's picture

Math module?

On the source force page, it's mentioned that OpenTK contains a maths module. Is this accurate? I don't see it anywhere.


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
objarni's picture

Noop 0,00440376ns
Vector3.Add(ref a, ref b, out res) 0,03766322ns
Add(a, b, out res) 0,08392346ns
res = Add(ref a, ref b) 0,09454702ns
res = Add(a, b) 0,11466584ns

Judging from this it seems what really matters (0,038->0.084) is pass-by-reference or pass-by-value, which makes me think your Vectors are structs and not classes? Is that the case? What are these numbers if you switch from struct to class (or class to struct)?

the Fiddler.'s picture

It isn't strange that object allocation is faster in .Net (or Java for example) than in C/C++, as they are garbage collected environments - allocation is little more than a plain addition there, an amortized O(1) operation. C/C++ have to traverse lists and run heuristics to find the best-fit hole in memory.

The problem with creating temporary objects in these mathematical operations isn't so much the memory allocation itself (a temp object on the stack isn't that bad), but the inevitable copy (8-16 bytes for a Vector struct). Operating on the vector members themselves may allow everything to stay in registers - performing a copy probably throws us to the L1 cache.

Yes, I know this is a premature optimization, but there really is no alternative here. We cannot go back and change the implementation afterwards, if it doesn't perform as it should - once released, we are stuck with it for eternity. At least, if you have a mutable type you can force immutable behavior out of it (for example by encapsulating it and providing a readonly property), but you cannot do the reverse and maintain performance.

When you are using mutable types, you are also "buying into" a less mathematically sane program with a higher probability of bugs.
Wait, aren't mathematical vectors *always* mutable?

the Fiddler.'s picture

These are structs. I followed Rico Mariani's advice, who knows much more on performance topics than I ever would:
The main reason goes right back to data density. There is a strong expectation that this class will be embedded in other classes such as Vertex and you can imagine many others. It is likely that there will be temporaries of this type, and importantly arrays of this type. Using a value type will make it so that walking through sets of related points does not require following multiple pointers and therefore data density is likely to be enhanced. And of course, there is storage overhead for classes -- the object header and the method table pointer -- these will be absent in packed arrays of structures.

Contrariwise the usual benefits of being a class are not especially valuable. Will we be subtyping? No. Sychronizing? No. Do we want point "identity" (e.g. the canonical instance of point (1,2,-1)? No.

Inertia's picture

2) Thanks for the link, interesting approach of the problem

4) True, in your implementation it didn't make a difference. In what I've written, instance members never allocate new memory and overwrite their values with the result instead, and only the static methods allocate new memory (the later is like XNA). Regarding XNA's vector math library: People complained about it because it returned a new instance every single call, at some point the GC kicked in and their applications froze.

But please, If you really must overwrite one of the parameters, PLEASE do not name them "left" and "right". Please refactor the one that's values are overwritten with "result" or something more obvious which parameter will be left untouched. (With whip-cream and sugar ontop!)

5) Just that the SetTo() method avoids duplicate code and allows to set XYZ to a desired value with a single call and without creating a new instance.
I've just used an array because it was the easiest way to feed Vectors to glVertex3fv etc.. There was this idea for a future optimization to use unmanaged C++ to malloc 3 floats from system memory for the Vectors at the initialization stage, to prevent pinning.

P.S. I take it you will use the math library also for future GL 3.0/OpenAL in OpenTK?

the Fiddler.'s picture

4) We've tried to avoid excessive memory allocations: static methods with "out" parameters (e.g. Vector3.Add(ref Vector3, ref Vector3, out Vector3) do not allocate memory. Only static methods that return results (e.g. Vector3 Vector3.Add(Vector3, Vector3)), do.

But please, If you really must overwrite one of the parameters, PLEASE do not name them "left" and "right".
No parameter named left or right will be overwritten, ever! Mutable parameters will be named "result" and have an 'out' flow direction. Some older SVN revisions had instance members that mutated the 'left' parameter, but this was just an experiment - no trace of that will make its way in a release!

5) You may know that already, but C# can allocate memory on the stack through the stackalloc keyword. The framework can also allocate heap memory with the Marshal.AllocHGlobal method. Neither of these is subject to garbage collection, and they should provide a measurable benefit in some cases (e.g. maintaining large client-side data structures for OpenGL, like vertex arrays).

P.S. I take it you will use the math library also for future GL 3.0/OpenAL in OpenTK?
Unless the rest of the Tao developers agree to add a math library to Tao, I'll probably keep this one around. If worse comes to worst, we can try to to find a middle ground with OpenAL.Net, to ensure interoperability (Tao.Ode is, I think, officially dead - superseded by Ode.Net). In any case, future GL 3.0/OpenAL, as well as current GL 2.1 make trivial use of the math library (just a few function overloads), so they will be easy to change, at least before the API freeze.

Inertia's picture

4) Then obviously I've been browsing the wrong directory, thought it was the correct one since the files were only ~20 hours old. Thank god, didn't really like interface as you probably noticed ;P

5) It was just an optimization consideration, I'll probably switch from Tao to OpenTK when OpenGL 3.0 specs/drivers are available so I'll rather help getting your math library bug-free than continuing my solution. From what I've read GL3 will be very nice to use in an object oriented design and due to the API changes it will probably be necessary to rewrite most of my helpers anyway.

Tbh I doubt there will be such a change in Tao. Most of the bindings do not require permanent maintenance and I believe to have read a similar suggestion (well, it was about making Tao more object oriented IIRC) before the forum wipe, which was rather clear about Tao will very likely stay at the bindings level and no step further. You had a good point there, before GL3 is released (and most likely GLFW, FreeGLUT, SDL and DevIL will all require updates) would be a good point of time to deal with this (if at all).

objarni's picture

Just out of curiosity, could you run your performance test changing struct to class and post it here? It would be interesting to know..

objarni's picture

The problem with creating temporary objects in these mathematical operations isn't so much the memory allocation itself (a temp object on the stack isn't that bad), but the inevitable copy (8-16 bytes for a Vector struct). Operating on the vector members themselves may allow everything to stay in registers - performing a copy probably throws us to the L1 cache.

Exactly; there is no reason to be "afraid" of new in C#!

About the inevitable copy of immutable types. If you do v += u; I don't see how you could get rid of the copy operation :) The copy is there whether vectors are mutable or immutable.

Yes, I know this is a premature optimization, but there really is no alternative here. We cannot go back and change the implementation afterwards, if it doesn't perform as it should - once released, we are stuck with it for eternity. At least, if you have a mutable type you can force immutable behavior out of it (for example by encapsulating it and providing a readonly property), but you cannot do the reverse and maintain performance.

There is no problem encapsulating an immutable type with a mutable one. Just keep a local (immutable) field, and when you want to do your += operation just create a new instance. But I'm not saying that is the best thing to do today.

The case where you want to update a single component, say x -- in that case the mutable += of is faster than "v = v + new Vector3(dx,0,0);". But really - how often do you update just a single component? This is a really odd operation..

Regardless of whether Vector3 become immutable or mutable, integrating it with the OpenTK GL methods, which I guess is one of the feats' driving the implementations of the math lib, is it such a great breaking change going from immutable to mutable or vice versa? The type is used in the interface if I'm not mistaken. Not by intricate algorithms, in which case it might have been a big change. And they way I see it, OpenTK is more of a wrapper-library than an algorithm library!


When you are using mutable types, you are also "buying into" a less mathematically sane program with a higher probability of bugs.
Wait, aren't mathematical vectors *always* mutable?

Umm. This is the same as saying, given some arithmetic expression say 1+2, "Isn't integers *always* changeable?" That is just madness in mathematics (you cannot *change* the value of the integer 2!). This is the distinction between mutable and immutable types.

the Fiddler.'s picture

I'll try to do that tonight. I expect performance will be similar to the (ref, ref, out) overload in microbenchmarks, but worse in larger cases.

the Fiddler.'s picture

Then you did browse the correct directory, but overlooked a tiny little thing in the code:

public static Vector3 Add(Vector3 a, Vector3 b)
{
 	a.X += b.X;
 	a.Y += b.Y;
 	a.Z += b.Z;
	return a;
}

Seems dangerous but it really isn't! The change to 'a' won't be reflected to the caller, because 'a' is a struct and we pass by value.

Moreover, the compiler will complain bitterly if you try to pass a null reference to:

public static void Add(ref Vector3 a, ref Vector3 b, out Vector3 result)

so this is safe too (in response to point (3) above).

I'm looking for ways to break the implementation, if you can think of any say it!