Hi, I'm Paul, but you can also call me Todd and I won't get upset.
Paul.Todd | 12 June, 2006 00:37
If you are not using RArray, you should be!
It offers a substantial performance improvement over the CArray.. implementations for almost all the cases I can think of.
Most of RArray is fairly simple to understand and works the way any generic array implementation works.
The biggest problem I do have with RArray is that the search and insert functions do not allow a variable to be passed to them so you are required to use a global or some other hack to get a state variable over into the comparison function.
The areas I am going look at are how RArray manages resources and how RArray can be applied effectively used to improve performance.
Before we can continue there are some questions that need to be answered:
How does RArray allocates memory?
If you play around with RArray you will soon find out that it allocates memory via the User::Alloc functions and these allocations are always a multiple of 4 bytes.
What RArray Constructors are there?
There is a default constructor, a constructor that specifies by how much the array will be expanded when it runs out of spare slots, and most importantly a constructor that maps onto a memory locations- but more of that later.
What is not present is a copy constructor, so what will happen then is that the C++ compiler will create a default constructor that is a bitwise copy of the item it is being copied from.
By default RArray does not have any items, however if one item is added, it preallocates by default 8 items, well okay KGranularity items so it can avoid memory fragmentation when adding items by avoiding a reallocate and copy each time an item is added.
Resource Management:
For the purposes of the example we will create a structure called TVector which has two integers in it.
THIS STRUCTURE IS A MUTIPLE OF 4 BYTES WHICH IS MANDATORY FOR RARRAY ELEMENTS!
struct TVector
{
TInt iSpeed;
TInt iDirection;
};
typedef RArray<TVector> RVectorArray; // Note that we keep the R prefix on our typedef
(1) static const TVector data[] = { {1,1}, {2,2}, {3,3} };
(2) const RVectorArray array(sizeof(TVector), CONST_CAST(TVector*,&data[0]), sizeof(data) / sizeof(data[0]));
(3) RVectorArray array1;
(4) array1 = array;
(5) TInt count = array.Count();
Each of the lines is numbered and documented as follows:
(1) we create a static const memory block. This could also be a block of memory from somewhere else such as a scanline or bitmap row.
(2) We now create an RArray that maps that memory into the RArray, note that the array is const so we can't expand it by accident.
It is important to read the documentation on this as it is very clear that this constructor does not allocate any memory nor take ownership of the memory, it just provides a mapping for developers to use the RArray with blocks of memory that needs to be manipulated.
(3) We now create a default RArray which has not memory allocated and it does not have any elements.
(4) Finally we create a clone of the array. It is vital to note that the compiler has underneath generated a default copy constructor and created a bitwise clone of data into array.
(5) Finally get the number of items in array after we have cloned it. What is the value of count?
Answer: See below
IMPORTANT NOTES:
(2) This just creates a mapping of the memory structure into the RArray is does not force the RArray to allocate any memory, hence there is nothing that needs to be put onto the cleanup stack. This is a very important point that a lot of people get wrong.
(4) This creates a clone of the array but ownership of the items in the data array is not taken
(5) Since array is a clone of data, count is 3
(6) When the function goes out of scope the arrays are destroyed, but we do not call reset. This is very important, because the arrays do not own any memory they MUST NOT be freed, so calling Reset is a big no-no! Likewise putting it on the cleanup stack is also a big mistake and will cause crashes.
Resource Management Part 2:
As I mentioned earlier, the underyling memory allocator for RArray is User::Alloc, so providing we allocate memory via this mechanism, we can move memory blobs from our structure into an RArray and then use the RArray.
(1) TVector* data2 = REINTERPRET_CAST(TVector*, User::AllocL(sizeof(data))); // actually sizeof(data) / (sizeof(data) / sizeof(data[0]))
(2) Mem::Copy(data2, data, sizeof(data));
(3) RVectorArray array2(sizeof(TVector), data2, sizeof(data) / (sizeof(data) / sizeof(data[0])));
(4) CleanupClosePushL(array2);
(5) const TVector element = {4,4};
(6) array2.Append(element);
(7) count = array2.Count();
(8) CleanupStack::PopAndDestroy(&array2);
Each of the lines is nummered and documented as follows:
(1) We allocate a block of memory using User::Alloc that is size of the 'data' structure (3 * sizeof(TVector))
This data is not put onto the cleanup stack, though of course we could put it onto the cleanup stack here and then remove it from the cleanup stack on line (4) and add the RArray back to the cleanup stack.
(2) We now copy the data from the const memory structure to our newly allocated memory blob
(3) We now create an RArray that maps the memory we just allocated to the internals of an RArray.
(4) Because we allocated the memory somewhere else, I decided that the RArray should now take ownership of the array, so that if any of the following lines should leave the memory will get correctly cleaned up by the RArray.
(5) we declare a new element
(6) This element is now added to our array that owns the memory. Where the array is full, the array will be expanded to cater for the new element, which may result in a memory reallocation, hence BE VERY CAREFUL where ownership of memory blocks is made.
(7) What should count now report? Answer: 4
(8) As we transfered ownship of the memory block to the array2 RArray, the cleanup stack may now delete it. If we did not put it onto the cleanup stack, we can just call Reset which has the same effect.
IMPORTANT NOTES:
(4) Is very important as you want to move ownership of the memory block to the RArray as soon as possible, especially if you want to add or remove items from the RArray.
(6) We should really check the return value from the append as it could have returned a negative value which would indicate that the item failed to expand. I have seen a number of cases where the error code is never checked and so unexpected things start happening.
(8) We do not neccessarily need to put the array2 as a parmeter to the PopAndDestroy but it provides an additional check that the item at the top of the stack is the item we expected it to be.
Some other things about RArray and RPointerArray
In Conclusion:
I hope this has provided an example of how to use resources in RArray where speed is critical. Needless to say, make sure that if you use this technique you keep it localized to one function so that the maintainer can know exactly what you are doing as these techniques need to be used to extreme care and only where there is time critical code.
Next week I want to discuss how I ported SyExpat to OS 9 which I have been promising to deliver for ages now. Its coded, I just hav'nt had a chance to document what I changed and why.
I only just posted a comment about multidimensional RArrays so I might publish some additional notes about these this week as well.
Hi, I'm Paul, but you can also call me Todd and I won't get upset.
RDF Facets:
qfnZtypeQUqfnTypeZBlogContentQ
qfnZtypeQUqfnTypeZCommunityContentQ
qfnZtypeQUqfnTypeZWebpageQ
qmarsZlanguageQUxhttpE3aE2fE2fswE2enokiaE2ecomE2flanguageE2d1E2fenX