Storing objects in a list
-
On 20/05/2013 at 23:16, xxxxxxxx wrote:
Originally posted by xxxxxxxx
The GeArray types are the only officially supported lists(arrays). And they are cross platform too AFAIK.
They are not the only supported types. Actually I rather recommend against using GeArray types, as they are rather slow, seen from nowadays standards. Use c4d_misc::BaseArray<> instead, if you need arrays. They are blazing fast, support sorting and iterators, and have almost no overhead.
Or, if you want it the old-fashioned way, how about a simple AtomArray?Originally posted by xxxxxxxx
So thank you, the std::list is precisely what I need!! Great!
Objection, your honor.
Originally posted by xxxxxxxx
Maxon does not like it when we use things like the Standard Library. If you use it, you are on your own.
Exactly
-
On 20/05/2013 at 23:42, xxxxxxxx wrote:
My fault, sorry. You first have to dereference the iterator.
(*it)->GetName()
Ingvar, using a container for this is really not a good idea I think. 1. the BaseContainer is intended
as a mapping type, 2. you need to do a cast every-time you want to access. Not that this will have
any cause on the performance on the program, but results in clunky and large code.You can use the GeDynamicArray class from the Cinema SDK as well (as I have already mentioned
in my first answer).#include <ge_dynamicarray.h> GeDynamicArray<BaseObject*> objects; // Store the top-level objects in a list. BaseObject* op = doc->GetFirstObject(); while (op) { objects.Push(op); op = op->GetNext(); } // Iterate over them again. LONG count = objects.GetCount(); for (LONG i=0; i < count; i++) { BaseObject* obj = objects[i]; // ... }
PS: Code is untested, intended to give you a small overview over the usage only.
Best,
-Nik -
On 21/05/2013 at 00:16, xxxxxxxx wrote:
...OR you can use a shiny BaseArray instead of the dusty GeDynamicArray
-
On 21/05/2013 at 00:21, xxxxxxxx wrote:
Is it much faster than the GeDynamicArray? I must admit that I have not yet taken a look
into the c4d_misc namespace. From the name, I always thought it would be a fixed size array. -
On 21/05/2013 at 01:41, xxxxxxxx wrote:
Never assume, always look The fact that BaseArray has methods like Push(), Insert() and Resize() tells you it's dynamic.
And about the speed: The BaseArray is not just faster, it's ridiculously fast. Really.
Here's some code I just wrote to benchmark it (as I didn't have any concrete numbers) :
void MyBench(LONG cnt) { GeDynamicArray<Real> dynamicArray; c4d_misc::BaseArray<Real> baseArray; LONG i; LONG timer = 0; Real x = 3.14165; GePrint("Array Benchmark (" + LongToString(cnt) + ")"); // Push() GePrint("GeDyamicArray::Push()..."); timer = GeGetTimer(); for (i = 0; i < cnt; i++) { dynamicArray.Push(x); } GePrint("..." + LongToString(GeGetTimer() - timer) + " msec."); GePrint("BaseArray::Push()..."); timer = GeGetTimer(); for (i = 0; i < cnt; i++) { baseArray.Append(x); } GePrint("..." + LongToString(GeGetTimer() - timer) + " msec."); // Reading GePrint("GeDynamicArray[]..."); timer = GeGetTimer(); for (i = 0; i < cnt; i++) { x = dynamicArray[i]; } GePrint("..." + LongToString(GeGetTimer() - timer) + " msec."); GePrint("BaseArray[]..."); timer = GeGetTimer(); for (i = 0; i < cnt; i++) { x = baseArray[i]; } GePrint("..." + LongToString(GeGetTimer() - timer) + " msec."); // Pop() GePrint("GeDynamicArray::Pop()..."); timer = GeGetTimer(); for (i = 0; i < cnt; i++) { x = dynamicArray.Pop(); } GePrint("..." + LongToString(GeGetTimer() - timer) + " msec."); GePrint("BaseArray::Pop()..."); timer = GeGetTimer(); for (i = 0; i < cnt; i++) { x = baseArray.Pop(); } GePrint("..." + LongToString(GeGetTimer() - timer) + " msec."); GePrint("Array Benchmark finished."); }
I built it using the latest Intel Compiler (version 13) as a 64 Bit Release build and ran it with different cnt values:
MyBench(10000); MyBench(100000); MyBench(1000000);
And here are the results (on a 27" iMac with 3.4Ghz i7 and 8GB RAM) :
10000 elements Push [] Pop GeDynamicArray 1 msec 0 msec 5 msec BaseArray 0 msec 0 msec 0 msec 100000 elements Push [] Pop GeDynamicArray 602 msec 0 msec 602 msec BaseArray 0 msec 0 msec 0 msec 1000000 elements Push [] Pop GeDynamicArray 272085 msec 0 msec 271149 msec BaseArray 9 msec 0 msec 0 msec
By the way, GeAutoDynamicArray and GeSafeDynamicArray are even slower.
-
On 21/05/2013 at 01:58, xxxxxxxx wrote:
Thanks Jack, this is a very useful resource! Those differences in speed are tremendous! You convinced
me rather using the BaseArray instead..Best,
-Nik -
On 21/05/2013 at 02:58, xxxxxxxx wrote:
Uhm, how do I copy a BaseArray to another BaseArray? Copy&Assign is disallowed for the BaseArray
class. I get compiler errors when doingarray1 = array2
"" error C2248: 'c4d_misc::BaseArray<T>::operator =' : cannot access private member declared in class 'c4d_misc::BaseArray<T>' ""
Thanks,
-Niklas -
On 21/05/2013 at 03:02, xxxxxxxx wrote:
Nevermin, just found the "CopyFrom" method.
-
On 21/05/2013 at 03:30, xxxxxxxx wrote:
Wow - what an interesting thread!
I thank you all for all new knowledge. I have a few comments though. My experience in general, is that while you can make speed tests, they are not always reliable. You have something called a compiler which lives its own superior life and is the ultimate decision maker. Certain ways of doing things might be fast in one situation, slow in another.
Anyhow - for the plugins I write, my speed concern is purely to speed up me. To get things done. My current plugins execute more than fast enough, regardless of list implementation.
But I like what I see about the BaseArray, so I will go for that one. -
On 21/05/2013 at 05:07, xxxxxxxx wrote:
Of course, the compiler is responsible for the final performance. Anyway, if one array type takes 272085 msec to accomplish a certain task, and another type takes 9 msec, it's pretty obvious that the first type will always be the slower one.
-
On 21/05/2013 at 07:34, xxxxxxxx wrote:
You can't blame me too much for not recommending the the BaseArray Frank.
Because the only rolled out in the in R14. And like most people. I'm still using older versions.I've been wondering what's the benefit for putting a class inside of a container?
The class is always there. And you can create an instance of it whenever you want. So I don't understand what benefit comes from stuffing it into a B.C.?
Where (in what case) would you need to use such a thing?-ScottA
-
On 21/05/2013 at 08:10, xxxxxxxx wrote:
Sorry to hear that BaseArray => R14
In any case, it is right up my alley, exactly what I need. And it works just great. -
On 21/05/2013 at 08:20, xxxxxxxx wrote:
It would now be interesting to see how this performs against a std::list Jack
-
On 21/05/2013 at 09:23, xxxxxxxx wrote:
I must say that I am impressed.
Didn't expect the AtomArray to be fairly fast! And the std library to be so slow.. And the
GeDynamicArray is really slow compared to all of those! Must be some very rusty piece of
list implementation?Benchmarks in the next post, the previous results were from a debug build.
Code:
#include <c4d.h> #include <ge_dynamicarray.h> #include <list> #include <vector> String* g_mode; void StartTest(String mode) { GePrint("Starting Test: " + mode); if (!g_mode) g_mode = new String; *g_mode = mode; } void AddStats(String type, LONG delta) { LONG l = type.GetLength(); for (LONG i=l; l <= 20; l++) { type += " "; } if (!g_mode) g_mode = new String("FOO"); GePrint(type + " " + *g_mode + ": " + LongToString(delta) + "ms"); } void Bench(LONG x) { AutoAlloc<AtomArray> atomarr; GeDynamicArray<BaseObject*> gda; c4d_misc::BaseArray<BaseObject*> ba; std::list<BaseObject*> list; std::vector<BaseObject*> vector; LONG i, tstart, delta; BaseObject* test = BaseObject::Alloc(Onull); if (!test) { GeDebugOut("No object could be allocated."); return; } AutoFree<BaseObject> test_free(test); GePrint("Benchmark started with " + LongToString(x) + " elements."); GePrint("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); StartTest("Adding Elements"); // Atom Array tstart = GeGetTimer(); for (i=0; i < x; i++) { atomarr->Append(test); } delta = GeGetTimer() - tstart; AddStats("AtomArray", delta); // GeDynamicArray tstart = GeGetTimer(); for (i=0; i < x; i++) { gda.Push(test); } delta = GeGetTimer() - tstart; AddStats("GeDynamicArray", delta); // BaseArray tstart = GeGetTimer(); for (i=0; i < x; i++) { ba.Append(test); } delta = GeGetTimer() - tstart; AddStats("BaseArray", delta); // std::list tstart = GeGetTimer(); for (i=0; i < x; i++) { list.push_back(test); } delta = GeGetTimer() - tstart; AddStats("std::list", delta); // std::vector tstart = GeGetTimer(); for (i=0; i < x; i++) { vector.push_back(test); } delta = GeGetTimer() - tstart; AddStats("std::vector", delta); StartTest("Iteration"); // AtomArray tstart = GeGetTimer(); for (i=0; i < x; i++) { (void) atomarr->GetIndex(i); } delta = GeGetTimer() - tstart; AddStats("AtomArray", delta); // GeDynamicArray tstart = GeGetTimer(); for (i=0; i < x; i++) { (void) gda[i]; } delta = GeGetTimer() - tstart; AddStats("GeDynamicArray", delta); // BaseArray tstart = GeGetTimer(); for (i=0; i < x; i++) { (void) ba[i]; } delta = GeGetTimer() - tstart; AddStats("BaseArray", delta); // std::list tstart = GeGetTimer(); std::list<BaseObject*>::iterator list_it = list.begin(); for (; list_it != list.end(); list_it++) { (void) *list_it; } delta = GeGetTimer() - tstart; AddStats("std::list", delta); // std::vector tstart = GeGetTimer(); std::vector<BaseObject*>::iterator vector_it = vector.begin(); for (; vector_it != vector.end(); vector_it++) { (void) *vector_it; } delta = GeGetTimer() - tstart; AddStats("std::vector", delta); } class Test : public CommandData { public: Bool Execute(BaseDocument* doc) { Bench(10000); Bench(100000); Bench(1000000); Bench(10000000); return TRUE; } }; Bool PluginStart() { return RegisterCommandPlugin(1000023, "Benchmark", PLUGINFLAG_COMMAND_HOTKEY, NULL, "Benchmark for List Types", gNew Test); return TRUE; } Bool PluginMessage(LONG type, void* pData) { return TRUE; } void PluginEnd() { }
-
On 21/05/2013 at 09:41, xxxxxxxx wrote:
Here it is, the new benchmark:
Benchmark started with 10000 elements. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Starting Test: Adding Elements AtomArray Adding Elements: 0ms GeDynamicArray Adding Elements: 0ms BaseArray Adding Elements: 0ms std::list Adding Elements: 1ms std::vector Adding Elements: 0ms Starting Test: Iteration AtomArray Iteration: 0ms GeDynamicArray Iteration: 0ms BaseArray Iteration: 0ms std::list Iteration: 1ms std::vector Iteration: 0ms Benchmark started with 100000 elements. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Starting Test: Adding Elements AtomArray Adding Elements: 2ms GeDynamicArray Adding Elements: 10ms BaseArray Adding Elements: 1ms std::list Adding Elements: 6ms std::vector Adding Elements: 2ms Starting Test: Iteration AtomArray Iteration: 0ms GeDynamicArray Iteration: 0ms BaseArray Iteration: 0ms std::list Iteration: 1ms std::vector Iteration: 0ms Benchmark started with 1000000 elements. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Starting Test: Adding Elements AtomArray Adding Elements: 20ms GeDynamicArray Adding Elements: 5060ms BaseArray Adding Elements: 18ms std::list Adding Elements: 43ms std::vector Adding Elements: 15ms Starting Test: Iteration AtomArray Iteration: 3ms GeDynamicArray Iteration: 0ms BaseArray Iteration: 0ms std::list Iteration: 7ms std::vector Iteration: 0ms Benchmark started with 10000000 elements. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Starting Test: Adding Elements **AtomArray Adding Elements: 180ms** **GeDynamicArray Adding Elements: 550471ms** **BaseArray Adding Elements: 170ms** **std::list Adding Elements: 454ms** **std::vector Adding Elements: 236ms** Starting Test: Iteration **AtomArray Iteration: 34ms** **GeDynamicArray Iteration: 0ms** **BaseArray Iteration: 0ms** **std::list Iteration: 70ms** **std::vector Iteration: 0ms**
Now that I have compiled in release mode, the differences between the std library and the
Cinema 4D API or not that huge anymore, but still signifficant!-Niklas
-
On 21/05/2013 at 09:58, xxxxxxxx wrote:
Thanks first of all Niklas! but of course you have to call .reserve() before iterating the vector container when doing push_backs to give a fair comparison (and resize for lists...).
dereferencing is slower than direct access which is not suprising but good to know!
-
On 21/05/2013 at 10:26, xxxxxxxx wrote:
Hi Katachi,
öhm, do I? Why should I reverse the list? All the methods I have used add the new element to
the end of the list.operator is
technically equal to *(arr + x) when operating on an array (or better, pointer).-Nik
-
On 21/05/2013 at 10:38, xxxxxxxx wrote:
Originally posted by xxxxxxxx
Hi Katachi,
öhm, do I? Why should I reverse the list? All the methods I have used add the new element to
the end of the list.Not reverse, re s er v e! (the vector. resize the list as there is no reserve for lists).
Btw. for performance purposes you may try the forward_list container as it uses single-linked list (so there should be no overhead over a c-style implementation).Originally posted by xxxxxxxx
And what do you mean with "direct access is faster than dereferencing"? using the [ x ] operator is
technically equal to *(arr + x) when operating on an array (or better, pointer).The iterator in the std containers, you dereference it, i.e. (*iter), for access to the actual data. With [s] you directly access the c-style array (you could do the same with the std containers btw if you'd iterate over the data).
-
On 21/05/2013 at 11:01, xxxxxxxx wrote:
Oh I'm sorry, misread it.
Well, usually when you use a list, you don't know how many elements it will have after storing
elements is done. This is why I think not calling reserve() is appropriate in this test.I don't think there is a big difference in speed regarding the dereferencing. As you can see from
the results above, the BaseArray as well as the std::vector iteration take almost no time even
with 10.000k elements.Best,
-Niklas -
On 21/05/2013 at 11:43, xxxxxxxx wrote:
Originally posted by xxxxxxxx
Oh I'm sorry, misread it.
Well, usually when you use a list, you don't know how many elements it will have after storing
elements is done. This is why I think not calling reserve() is appropriate in this test.That's why you don't use resize for the vector but you definetly use reserve if you are about to push largely. It has a huge performance impact on the following push operations. Even if you don't know the exact size a good guess will increase performance.
Originally posted by xxxxxxxx
I don't think there is a big difference in speed regarding the dereferencing. As you can see from
the results above, the BaseArray as well as the stdvector iteration take almost no time even
with 10.000k elements.Maybe it is the double-linked list structure of the list container that makes the difference. Would be well worth exploring (at least I'll do) to see what exactly is causing the slow down (and how the forward_list performance is in contrast).
Edit: Don't know why but your quoted text is out of screen for me Anyway, thanks for the tests so far.