-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Description
Description
I am porting a huge .NET application (a game written on custom engine) to browser-wasm runtime and noticed that heap fragmentation is pretty high. The initial idea was that this may be caused by major heap blocks fragmentation. I tried to play with MONO_GC_PARAMS
increasing/decreasing nursery-size
, evacuation-threshold
and soft-heap-limit
, and triggering gc frequently. But there was no noticeable difference in FragmentedBytes
metric.
Then I used emscripted memory profiler and found that there is a lot of allocation with stack traces like this:
at dotnet.native.wasm.dlmalloc (http://localhost:8081/_framework/dotnet.native.wasm:wasm-function[12628]:0x29d1ca)
at dotnet.native.wasm.internal_memalign (http://localhost:8081/_framework/dotnet.native.wasm:wasm-function[12634]:0x29e12b)
at dotnet.native.wasm.dlposix_memalign (http://localhost:8081/_framework/dotnet.native.wasm:wasm-function[12635]:0x29e2d8)
at dotnet.native.wasm.mono_valloc_aligned (http://localhost:8081/_framework/dotnet.native.wasm:wasm-function[931]:0x4af61)
at dotnet.native.wasm.sgen_alloc_os_memory_aligned (http://localhost:8081/_framework/dotnet.native.wasm:wasm-function[1508]:0x6b380)
at dotnet.native.wasm.sgen_los_alloc_large_inner (http://localhost:8081/_framework/dotnet.native.wasm:wasm-function[1424]:0x6094c)
at dotnet.native.wasm.sgen_alloc_obj_nolock (http://localhost:8081/_framework/dotnet.native.wasm:wasm-function[1280]:0x57658)
I prepared a sample repo with reproduction. The sample is named "Los fragmentation", and it allocates a lot of byte arrays of configured size. After playing with parameters I found that if array size is greater than 7984 (SGEN_MAX_SMALL_OBJ_SIZE - mono object size
), fragmentation grows significantly. Sample code:
[JSExport]
public static async Task LosFragmentation(int count, int batchSize, int size)
{
var losHolder = new List<byte[]>();
for (int i = 0; i < count; i++)
{
PrintMemory($"LF #{i} - start");
for (int j = 0; j < batchSize; j++)
{
losHolder.Add(new byte[size]);
}
await Task.Delay(16);
GC.Collect();
PrintMemory($"LF #{i} - end");
}
}
Here is output with different parameters:
1. count=10, batchSize=1000, size=8192
LF #9 - end | Total Memory: 82.37 MB | HeapSizeBytes: 236.64 MB | FragmentedBytes: 158.29 MB | TotalCommittedBytes: 78.35 MB | G0: 0 | G1: 10 | G2: 10 |
1. count=10, batchSize=1000, size=7984
LF #9 - end | Total Memory: 80.39 MB | HeapSizeBytes: 80.77 MB | FragmentedBytes: | TotalCommittedBytes: 84.00 MB | G0: 10 | G1: 10 | G2: 10 |
Configuration
.NET 10.0.100-preview.5.25277.114
Chrome 138.0.7204.158
Data
Workaround
Please explain if it is expected behavior, are there any known workarounds (including building custom runtime) and is there any planned work on this issue.