Add section about heap and stack allocation limits

James Groom 2024-08-30 22:54:08 +10:00
parent dafcc8b9ad
commit e9f19d0be6
1 changed files with 24 additions and 0 deletions

@ -3,6 +3,30 @@ To save us repeating our complaints about the lack of proper documentation under
Contribute to [the official docs](https://github.com/dotnet/docs) if possible. Contribute to [the official docs](https://github.com/dotnet/docs) if possible.
## Allocation limits
Under .NET 8:
- The largest 1D byte array is `new byte[Array.MaxLength]`, `Array.MaxLength` being hardcoded to `0x7FFFFFC7` or just under 2 GiB. (Would probably want to alloc in 1 GiB chunks to have nice code, maybe even smaller to keep the GC happy.)
- The largest 1D struct array is `new T[Array.MaxLength]`. So its size is... unbounded since structs don't have a size limit apart from the stack size (and `[InlineArray]`'s 1 MiB cap). (TODO check there isn't an undocumented cap)
- The largest *n*-D byte array is `UNK()` (`LongLength` is `0xUNK` or UNK GiB).
- All of that is on the managed heap. The default/global stack is OS-specific and cannot be changed (feature request to come): On Windows, it's 1.5 MiB [due to an oversight](https://github.com/dotnet/runtime/issues/96347#issuecomment-1871528297), and on Linux, it's inherited from the OS, typically 8 MiB.
- The stack size for threads can be [chosen at runtime](https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.-ctor?view=net-8.0#system-threading-thread-ctor(system-threading-threadstart-system-int32)), though as with all of this you may have an XY problem and should reconsider.
Under Mono (x64 unless specified):
- The largest 1D byte array is `new byte[int.MaxValue]` (just under 2 GiB), as there is no way to instantiate an array with a larger size (`Array.CreateInstance(typeof(T), 0x80000000L)` will throw `ArgumentOutOfRangeException: Arrays larger than 2GB are not supported.`, which I suppose is technically incorrect).
- Similarly, the largest 1D struct array is `new T[int.MaxValue]`. So its size is... unbounded since structs on the heap don't have a size limit.
- The largest *n*-D byte array is `new byte[2, 0x7FFFFFE6]` (`LongLength` is `0xFFFFFFCC` or just under 4 GiB). Allocating a single byte more gives an OoME. Multidimensional arrays of other structs appear to also be limited to `0xFFFFFFCC` octets.
- If a call would allocate beyond those limits (or the process' or machine's limits), the relevant builtin method will throw an `OutOfMemoryException`, which can be caught and handled but definitely shouldn't be.
- All of that is on the managed heap. The default/global stack is UNK MiB large and can be changed [externally with `ulimit`](https://github.com/dotnet/runtime/issues/96347#issuecomment-1981511546) (a builtin in most POSIX shells, including BASH), but not with Roslyn. If a `stackalloc` call would allocate beyond those limits (with `stackalloc`)
- The stack size for managed threads can be chosen at runtime, though as with all of this you may have an XY problem and should reconsider. The max. is [1 MiB / 2 MiB (32-bit/64-bit hosts)](https://stackoverflow.com/a/19909421), and there is a [hack to bypass the limit](https://stackoverflow.com/a/48003390) (untested).
Under .NET Framework on Windows (x64 unless specified):
- Untested, but I'm guessing it's probably the same as Mono so long as the [`gcAllowVeryLargeObjects`](https://learn.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcallowverylargeobjects-element) is enabled in the runtime config, going by those docs and [this blog post](https://www.centerspace.net/large-matrices-and-vectors).
- Or maybe not; see [this SO answer](https://stackoverflow.com/a/34413257) and the comment below it.
- The default/global stack is 1 MiB, specified in a PE header. This can be changed [externally with `EDITBIN`](https://stackoverflow.com/a/54584830), but not with Roslyn nor at runtime in any way.
- The stack size for threads [defaults to 1 MiB / 4 MiB (x86/x64)](https://stackoverflow.com/a/5507910) (TODO surely there's an official source) can be [chosen at runtime](https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.-ctor?view=netframework-4.8#system-threading-thread-ctor(system-threading-threadstart-system-int32)), though as with all of this you may have an XY problem and should reconsider. The max. is UNK MiB (default AppDomain; 1 MiB if untrusted) and the min. [seems to be OS-dependent](https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.-ctor?view=netframework-4.8#system-threading-thread-ctor(system-threading-threadstart-system-int32)) but was [~256 KiB on Vista](https://techcommunity.microsoft.com/t5/windows-blog-archive/pushing-the-limits-of-windows-processes-and-threads/ba-p/723824) and presumably hasn't been changed since.
- It should be no surprise that all these limits were inherited from native Win32. Thankfully modern .NET is deviating from that where necessary.
## BCL source ## BCL source
The API reference on Microsoft Learn (formerly MSDN) now links to the source in most places, but of course that's for modern .NET. The API reference on Microsoft Learn (formerly MSDN) now links to the source in most places, but of course that's for modern .NET.