Overhead in detail
The final outcome is that using the Create magic constructor can incur quite a bit of overhead:
- a DynArrayClear call (not sure why it’s there), that will release the previously assigned block of memory for the temporary array
- a DynArraySetLength, that will allocate a new block of memory and zero it
- a DynArrayAssign, that will trigger the release of the memory for the existing array (if it wasn’t empty), along with a bus lock for the reference count overhead
- extra initialization and finalization for the temporary array
In a multi-threaded applications, all that extra memory management and bus locking is going to have a disproportionate impact on performance. If you test the above snippets in a multi-threaded environment, you’ll notice that when using the array constructor, execution quickly becomes single threaded, bottle-necking on the memory manager and bus locks.
The manual initialization only has a single DynArraySetLength call, and if the array is not empty, this may not result in a new block being allocated (as the existing memory block could just be resized in place). So if you initialize the same array variable more than once, the manual form can be quite cheap.
A better array initializer?
Now that I showed you the magic array Create constructor is no good, what if you still want something convenient? Well open arrays can come to the rescue:
procedure InitializeArray(var a : TIntegerArray; const values : array of Integer); begin SetLength(a, Length(values)); if Length(values)>0 then Move(values, a, Length(values)*SizeOf(Integer)); end; ... InitializeArray(a, [1, 2]);
The above function won’t be as efficient as manual initialization: there is an extra function call and the values will be copied twice. However it eliminates all the extra memory management and bus locking, so will scale quite better in multi-thread, while being compact syntax and code-wise.
Note that for a managed type (String, Interface…) then System.Move can’t be used, you’ll need to use either asm hackery or a for-to-do loop with item-by-item assignment, which will incur a performance hit, and often make it non-competitive with the manual version.
Need even more speed?
In the grand scheme of things however, all the above approaches suffer from the SetLength call, which is quite complex (have a look at DynArraySetLength in the System.pas unit… and weep), so if you know there is a chance the dynamic array wasn’t resized, in the manual version, you can gain by doing
if Length(a)<>Length(value) then SetLength(a, Length(Values));
Which can when the SetLength is waived, net you more than a mind boggling 10x speedup (ten times).
Ouch! Why doesn’t the RTL do that?
Well, it doesn’t do that because it can’t, as Delphi’s dynamic arrays are not some kind of hybrid half-way between a value type and a reference type, and SetLength is the key stone where all the hybridization happens (for more on the subject, see Dynamic Arrays as Reference or Value Type).
a := [1, 2];