// initialize array to fit all expected items + a little wiggle room
SetLength
(
array
,
initial length
+
x
);
// current item index
curr_index
:=
0
;
begin
// fill in the array and increase index with each line
// if curr_index (+1) > Length(array) : resize array to new length to fit more items
Inc
(
curr_index
);
end
;
// finalize array
SetLength
(
array
,
curr_index
);
I thought that I don't really need to SetLength, if it's already filled to the max:
// finalize array, only if needed
if
curr_index
<>
Length
(
array
)
then
SetLength
(
array
,
curr_index
);
So, I was looking into the
DynArraySetLength
to see what it does if you try to resize to the same length as it is. And DynArraySetLength checks for 0, <0, erros... but it doesn't check for NewLength = OldLength. This happens only a few calls later in ReallocMem -> GetMem -> AllocateLargeBlock. And it does some memory related stuff,
but eventually doesn't resize or allocates new memory space.
Not only with SetLength but with other situation where loops with hundreds or thousands of iterations will be performed, eg. in a case where searching for TBytes in an array, sometimes i do check length before call the compare, in other places where the length is the same but i have thousands of hash values, so i check the first byte then call the compare, thus way i reduced the overhead of the call to 1 in 256 instead of paying it each compare, sometimes i do that with strings !
procedure
DynArraySetLength
(
var
a
:
Pointer
;
typeInfo
:
Pointer
;
dimCnt
:
NativeInt
;
lengthVec
:
PNativeint
);
//....
if
SysHasWeakRef
(
PTypeInfo
(
ElTypeInfo
))
then
begin
if
newLength
<
oldLength
then
minLength
:=
newLength
minLength
:=
oldLength
;
GetMem
(
pp
,
neededSize
);
FillChar
((
PByte
(
pp
)
+
SizeOf
(
TDynArrayRec
))^,
minLength
*
elSize
,
0
);
if
p
<>
nil
then
begin
// ---> here <---
MoveArray
(
PByte
(
pp
)
+
SizeOf
(
TDynArrayRec
),
PByte
(
p
)
+
SizeOf
(
TDynArrayRec
),
ElTypeInfo
,
minLength
);
if
newLength
<
oldLength
then
FinalizeArray
(
PByte
(
p
)
+
SizeOf
(
TDynArrayRec
)
+
newLength
*
elSize
,
ElTypeInfo
,
oldLength
-
newLength
);
FreeMem
(
p
);
end
;
2 - The operation executes on O(n) when the array is multidimensional
:
// Take care of the inner dimensions, if any
if
dimCnt
>
1
then
begin
Inc
(
lengthVec
);
Dec
(
dimCnt
);
i
:=
0
;
while
i
<
newLength
do
begin
DynArraySetLength
(
PPointerArray
(
p
)[
i
],
ElTypeInfo
,
dimCnt
,
lengthVec
);
Inc
(
i
);
end
;
except
// Free arrays on exception
for
j
:=
0
to i
do
_DynArrayClear
(
PPointerArray
(
p
)[
j
],
ElTypeInfo
);
_DynArrayClear
(
p
,
typeInfo
);
raise
;
end
;
end
;
//---------------------------------
LArray
:
array
of
array
of
Integer
;
begin
SetLength
(
LArray
,
100
,
10
);
SetLength
(
LArray
,
100
,
10
);
// DynArraySetLength x100
end
;
3 - The function can ruin the cache ! because it dereferences some rtti data and it relies on MM to check for the block size. If the check was implemented inside the function than we won't dereference any unnecessary data.
4 - Adding a simple check to compare old vs new length will be much better and avoids all the issues above.
GetMem
(
pp
,
neededSize
);
FillChar
((
PByte
(
pp
)
+
SizeOf
(
TDynArrayRec
))^,
minLength
*
elSize
,
0
);
MoveArray
(
PByte
(
pp
)
+
SizeOf
(
TDynArrayRec
),
PByte
(
p
)
+
SizeOf
(
TDynArrayRec
),
ElTypeInfo
,
minLength
);