1

Suddenly we faced the following problem that the allocation of the multidimensional array consumes more memory than it is required for the array itself.

Minimal code to reproduce:

type
  TConfig1 = packed array [1..16777216] of Byte; { Works }
  TConfig2 = packed array [1..256, 1..256, 1..256] of Byte; { Works }
  TConfig3 = packed array [1..8, 1..8, 1..8, 1..8, 1..8, 1..8, 1..8, 1..8] of Byte;

  TClass1 = class
  private
    FConfig: TConfig1;
  public
    constructor Create;
  end;

  TClass2 = class
  private
    FConfig: TConfig2;
  public
    constructor Create;
  end;

  TClass3 = class
  private
    FConfig: TConfig3;
  public
    constructor Create;
  end;

constructor TClass1.Create;
begin
  var X := FConfig[1];
end;

constructor TClass2.Create;
begin
  var X := FConfig[1];
end;

constructor TClass3.Create;
begin
  var X := FConfig[1];
end;

begin
  Writeln(SizeOf(TConfig1)); { Outputs 16777216 }
  Writeln(SizeOf(TConfig2)); { Outputs 16777216 }
  Writeln(SizeOf(TConfig3)); { Outputs 16777216 }
  TClass1.Create.Free; { success }
  TClass2.Create.Free; { success }
  TClass3.Create.Free; { stack overflow }
end.

So, for some reason, the number of dimensions limits the amount of memory possible for allocation in the class field.

When I do 1 or 2 dimensions only, I can allocate much more than 16MB! But, once the dimension count grows, it feels like it hits a 16MB limit (which is by default a minimum stack size).

Am I missing something?

5
  • 1
    I can reproduce in 12.2. Allocating memory for the array is not the problem. If you use the debugger to step through the disassembly, you will see that the preamble of the constructor call is attempting to push thousands of values (indexes?) - about 2MB worth - onto the call stack before it even begins constructing the class. That is where the stack overflow is coming from. If you remove the read of FConfig[1] then it doesn't do that. I'm not sure what it's trying to do. Commented May 6 at 17:12
  • Yes we also find that.. that was really some nonsense.. mov ecx, ??: push 0: push 0; dec ecx; jnz ???; and that just throwing stack overflow.. Commented May 6 at 19:31
  • Yes, and if you do not access array it doesn't happen een if it's public! O first though it's optimization of unused private field.. but it's not. As far as i know Delphi can't do that even - it could break the code and alignment in memory :) Commented May 6 at 19:34
  • 1
    That's not the codegen I see. I see this instead: mov eax,$00000200; add esp,$fffff004; push eax; dec eax; jnz ... So, a loop with 512 iterations that adjust ESP and push EAX (the loop counter). All before System._ClassCreate() is called. The push eventually overflows the stack. Commented May 6 at 20:04
  • Yeah.. we were adjusting different versions and it sometimes is that and other time was the one you write :) Commented May 6 at 20:14

2 Answers 2

2

Expanding on SilverWarrior's answer to explain how/why the stack size is being exceeded:

For TConfig1, FConfig[1] is a single byte

For TConfig2, FConfig[1] is a 256x256 array (65536 bytes)

For TConfig3, FConfig[1] is an 8x8x8x8x8x8x8 array (2,097,152 bytes or 2MB)

Since the default stack size is 1MB, a 2MB value will exceed the available stack.

If you are working with such values extensively, you will need to increase your default stack size considerably; even doubling the stack to 2MB will not be enough to accommodate more than a single first-dimension value of such an array.

Also, be aware that the project default stack size applies to all threads created by your process at runtime; if your process creates many threads, a larger default stack size could exhaust system RAM and is likely unnecessary for threads not working with such data structures.

To avoid the problem entirely, you may wish to isolate code working with such data structures in a specific thread with an explicitly allocated stack sufficient for that thread's work or use a different approach to structuring your data.

Sign up to request clarification or add additional context in comments.

3 Comments

Yep, increasing stack size works. Anyway we refactored that odd code with multidimensional (7 dimensions) array to use a IDictionary<TKey, TSomeRecord>. TKey just packed record containing all 7 values for all dimensions needed - this way no problem with all of this nonsense :)
@Z.B. What kind of data do you even store in this data structure and what operations are you doing on this data? Since you are storing this data as fixed multi-dimensional array which is part of class you could potentially store the data in one sigle dimensional array and then write an Indexed property to access specific item from the array. While documentation is showing only example with a single Index properties can actually have multiple indexes. ...
... In the end the Indexes are just passed into the property getter or setter method where you can calculate the correct position of desire item inside your single dimensional array. This approach would allow you to have almost infinite number of dimensions without the high stack usage.
1

The reason why you are getting the stack overflow error is because you are actually attempting to exceed default maximum stack size.

You should increase the Maximum stack size in Project > Options > Building > Delphi Compiler > Linking.

The default value is 1048576 which you are exceeding in your case.

4 Comments

In the first place it should not use stack for class fields... And if the 3rd line fails then how first two works? They are all 16mb.
@Z.B. "it should not use stack for class fields" - It is not using the stack for the class field itself. It is using the stack to prepare access to the field. You are making a copy of the field's data into a local variable. The bigger the copy, the more stack space is used, apparently.
Thanks for explaining. Still super strange that it works with less dimensions!
@Z.B. Fewer dimensions = fewer values being pushed on the stack. For TClass1, nothing is pushed at all. For TClass2, 16 values are pushed. For TClass3, 512 values are pushed. My guess is that it is setting up some kind of lookup table for the extra dimensions.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.