-1

As far as I know Delphi always allocates a generous space for dynamic arrays to avoid relocation when the dynamic array's size is increased (using SetLenghth(), e.g.) However, in many cases just a single SetLength will occur, when creating the class, so once the array has got dimensioned it can be considered henceforth a “constant-length” one. That is, there is absolutely no need to allocate a bigger-then-necessary space to the array.

So, is there a directive, a trick etc. for telling Delphi that the array in question is actually a “fake constant length” array, that is, its length should be set in a conservative way?

Zoltán Bíró
  • 346
  • 1
  • 12
  • I don't agree with your reasoning in the first sentence. Where in the docs have you read that extra space would be allocated, "just in case"? Can you provide an example? – Tom Brunberg Jun 05 '23 at 12:30
  • I read somewhere, don't remenber when an where. But anyway, it's logical. If **Delphi** allocated just the strictly necessary amount of memory every time, the `Setlength(a, Length(a) + 1)` always would lead in relocation on each length increment. – Zoltán Bíró Jun 05 '23 at 13:37
  • @ZoltánBíró: Doing `SetLength(X, Succ(Length(X)))` is always something you want to avoid, even if you don't need to actually move any data. – Andreas Rejbrand Jun 05 '23 at 13:43
  • But let's try. `Setlength(a, 1); Setlength(a, Length(a) + 1)`. Will the data pointer in the dynamic arra change or not? – Zoltán Bíró Jun 05 '23 at 13:44
  • What you read was that the Delphi memory manager has 3 bucks of memory slots - small/medium/large. Especially the small bucket has a granularity of fixed sizes e.g. 8, 48 and so on to ease memory reallocation and memory reusage. I think the medium buffer doest that too in some sort at least it does not put back the memory to the OS via 'VirtuallFree' and the large bucket always sends back the memory to the operating system so it can there be reused. – mrabat Jun 05 '23 at 13:45
  • @AndreasRejbrand, it's still one of the most frequent operation for a bynamic array. – Zoltán Bíró Jun 05 '23 at 13:45
  • @ZoltánBíró: Not in high-quality code. Definitely not. See https://stackoverflow.com/a/75158947/282848 for an example (yes, that's a string, but the same principles apply). – Andreas Rejbrand Jun 05 '23 at 13:46
  • Well, you're not right, the question is indeed relevant. I've performed a `setlength(a, i);` for `i := 1 to 80`. And the result is quite interesting. After the first `SetLength` a reallocation has happened, but for the next 8 increments the `@a[0]` has been kept unchanged, that is, the **Delphi** already has left a spare for further increments. Then after 16 increments, 32 increments and so on. The conclusion is that **Delphi** is surprisingly intelligent and behaves exactly as expected: first time it allocates just the exact space, then the threshold will be continously increased. Bingo! – Zoltán Bíró Jun 05 '23 at 14:36
  • 1
    @ZoltánBíró What you observed is an implementation detail of the memory manager - more specific of its `ReallocMem`. The code in `DynArraySetLength` has nothing to do with that. – Stefan Glienke Jun 05 '23 at 14:53
  • @StefanGlienke, the only thing we're interested in is to avoid unnecessary performance penalties when the length can be increased directly, without allocaring/relocating. And to be honest, don't really think Delphi would be that stupid to call allocation routines (actually do-nothing ones) even in such cases. – Zoltán Bíró Jun 05 '23 at 15:09
  • @ZoltánBíró: Yes, I 100% agree with you that `SetLength` often doesn't need to copy the memory, that is, the pointer is the same before and after. I have never said I disagree with this fact. Still, even with this being the case, I very much believe that `SetLength(X, Succ(Length(X)))` is an anti-pattern unless `X` is always something small or performance isn't important. Did you see https://stackoverflow.com/a/75158947/282848? – Andreas Rejbrand Jun 05 '23 at 15:19
  • Well, if performance is critical, even the classic concept of dynamic arrays might not be a good starting point, I'd rather use dynamically allocated static array objects (or even smarter data structures) in such cases. But, if the performance is “just” important and SetLength calls are not that frequent, SetLength(X, Succ(Length(X))) it might still have its place even in high quality codes. – Zoltán Bíró Jun 05 '23 at 15:28
  • Dynamic arrays always allocate just as much memory as it is needed. to store its data Whether the array data needs to be relocated to new position in memory doesn't so much depends on the new size of an array but instead of whether the memory past the currently reserved by an array is still free or has already been assigned to something else. This is due the fact that arrays always need to be stored in one continuous memory block. This means that in theory you could increase array size multiple times or even by large amounts without the need for any relocation. – SilverWarior Jun 05 '23 at 19:04
  • As for what you call _generous space allocation_ I'm guessing you might be referring to how certain Lists used to handle resizing of their dynamic arrays that store their items. You can for instance get the size of internal list array using [TList.capacity](https://docwiki.embarcadero.com/Libraries/Alexandria/en/System.Classes.TList.Capacity) property. – SilverWarior Jun 05 '23 at 19:14
  • Now looking at code that handles changing the list internal array size in Delphi 11.2 I see that it is increasing array just enough to store all items. But I do remember that in older Delphi versions internal array size would be doubled until it reached certain size at which point it would be increased by fixed sized chunks. I'm guessing this might have been since old times when default Delphi manager was much less efficient in memory allocations. – SilverWarior Jun 05 '23 at 19:17

1 Answers1

-1

Fortunately the default dynamic array allocation behaviour does exactly what we expect it would do if it was enough smart:

  1. The first SetLength(a, n) allocation makes room just for exactly n items and no more, without spare.
  2. Subsequent array length increases are optimized for reducing relocations (hence avoiding performance penalties), even by sacrifying space.

So, there is no need for such directives, Delphi does what we expect from it by default.

Zoltán Bíró
  • 346
  • 1
  • 12
  • 2
    If you look at the source code for `System.DynArraySetLength()`, the RTL simply asks the memory memory for just the exact amount of memory needed to hold exactly `n` items requested. Even when resizing an existing array. However, the memory manager itself can (and likely will) pad that memory as it sees fit. If the existing memory is of sufficient size, no reallocation occurs. So, the RTL itself is not the one avoiding reallocations of dynamic arrays. Any optimization would be from the memory manager itself - which can be replaced at runtime with any MM the user wants. – Remy Lebeau Jun 05 '23 at 16:26
  • @RemyLebeau, so the Delphi implementation is not the worst, but still far from optimal. Though it's somewhat understandable, as the dynamic array object has no possibilities to store the actual size of the buffer, this is the task of the memory manager! Well, it still would be a simplistic solution, that is, a simple algorythm or a global table which can show whether it's safe to increase, without calling the MM. – Zoltán Bíró Jun 05 '23 at 19:02
  • It is not really necessary to avoid the MM call. The RTL simply asks the MM for the new buffer size, and the MM rounds it up if needed, and if the array's current buffer is sufficient for the new size than no reallocation occurs. – Remy Lebeau Jun 05 '23 at 19:26
  • These unnecessary function calls still consume time. Well, far not that much like a relocation would occure, but still not the best. – Zoltán Bíró Jun 05 '23 at 19:43