Reputation: 123
I need an array, that is optimized for one-time-initialization at runtime, with a given length. So memory should be allocated at runtime, but I don't need to change its length.
Is there a array-type other than the pure dynamic array? (it seems to be not the optimal choice for this task)
Bonus would be, if the initialized array is indexable via pointer-iteration, so all it's elements are allocated consecutive in memory.
Is this all just a daydream of a non-experienced programmer, or is there a possibility to achieve this?
I could imagine to do this with manual memory allocation, but maybe there's another way.
Edit:
My main concern is the reading and writing speed of the array.
Upvotes: 0
Views: 2082
Reputation: 612794
You can encapsulate what you desire in a generic type. Like this:
type
TFixedLengthArray<T> = record
strict private
FItems: TArray<T>;
FLength: Integer;
function GetItem(Index: Integer): T; inline;
procedure SetItem(Index: Integer; const Value: T); inline;
public
property Length: Integer read FLength;
property Items[Index: Integer]: T read GetItem write SetItem; default;
class function New(const Values: array of T): TFixedLengthArray<T>; static;
end;
{ TFixedLengthArray<T> }
class function TFixedLengthArray<T>.New(const Values: array of T): TFixedLengthArray<T>;
var
i: Integer;
begin
Result.FLength := System.Length(Values);
SetLength(Result.FItems, Result.FLength);
for i := 0 to Result.FLength-1 do begin
Result.FItems[i] := Values[i];
end;
end;
function TFixedLengthArray<T>.GetItem(Index: Integer): T;
begin
Result := FItems[Index];
end;
procedure TFixedLengthArray<T>.SetItem(Index: Integer; const Value: T);
begin
FItems[Index] := Value;
end;
Create a new one like this:
var
MyArray: TFixedLengthArray<Integer>;
....
MyArray: TFixedLengthArray<Integer>.New([1, 42, 666]);
Access items like this:
for i := 0 to MyArray.Length-1 do
Writeln(MyArray[i]);
This just wraps a dynamic array. Elements are contiguous. The length of the array is determined once and for all then a new instance is created.
One thing to watch out for here is that the type will behave like a reference type since its data is stored in a reference type. That is, the assignment operator on this type will behave in the same manner as dynamic array assignment.
So if we have two variables of this type, arr1
and arr2
then the following occurs:
arr1 := arr2;
arr1[0] := 42;
Assert(arr2[0] = 42);
If you wanted to make the type behave like a true value then you would implement copy-on-write inside SetItem
.
Update
Your edit to the question changes is significantly. It seems that you are in fact concerned more with performance than encapsulation.
The inlining of the item accessor methods in the above type means that the performance characteristics should be close to that of an array. The access will still be O(1)
, but it is quite plausible that the inliner/optimiser is weak and fails to emit the most optimal code.
Before you decide that you must use arrays to obtain the absolute ultimate performance, do some real world benchmarking. It seems to me to be quite unlikely that the code to read/write from an array is really a bottleneck. Most likely the bottleneck will be what you then do with the values in the array.
Upvotes: 3
Reputation: 43023
Just use an external Count: integer
variable, and use the dynamic array length as the "capacity" of the array. It would avoid most memory allocation, if the initial capacity of the array is well defined.
In practice, TList<T>
, as defined in the System.Generics.Collections
unit, is using this scheme: it stores internally an array, but it has its own Count
property. I suspect this is what you were looking for.
For a more low-level stuff, with more features (like JSON or binary serialization, or fast lookup via a hash of one or several properties), you may take a look at our TDynArray dynamic array wrapper. Those are just wrappers on existing dynamic arrays, not data holder like TList<T>
. And they work from Delphi 5 or older, and also FPC.
Upvotes: 4