emscripten-core / emscripten

Emscripten: An LLVM-to-WebAssembly Compiler
Other
25.35k stars 3.25k forks source link

Unable to reliably determine memory usage at runtime #22115

Closed suzukieng closed 1 week ago

suzukieng commented 1 week ago

Version of emscripten/emsdk: 3.1.56

Full link command and output with -v appended:

These are the emscripten options I pass to emcc:

export EMSCRIPTEN_OPTS="-sWASM=1 \
    -sSINGLE_FILE=1 \
    -sMODULARIZE=1 \
    -sINITIAL_MEMORY=32MB \
    -sSTACK_SIZE=5MB \
    -sALLOW_MEMORY_GROWTH=1 \
    -sABORTING_MALLOC=1 \
    -sFILESYSTEM=0 \
    -sENVIRONMENT=web \
    -sEXPORTED_RUNTIME_METHODS=ccall,cwrap"

Description

I'm trying to determine the memory usage of a library at run-time. It processes frames from the device's camera so I'm primarily interested in the amount of memory required at run-time with a certain feature set enabled, as well as catching any leaks (I know LSAN exists, but this question does not relate to it).

At run-time I occasionally log the WASM Memory usage as reported by maillinfo(). To determine used memory, I use the same logic as in this emscripten unit test: https://github.com/emscripten-core/emscripten/blob/main/test/core/test_mallinfo.c#L20

#include <malloc.h>

...

EMSCRIPTEN_KEEPALIVE int mem_used() {
    struct mallinfo info = mallinfo();
    uintptr_t dynamicTop = (uintptr_t)sbrk(0);
    return dynamicTop + info.fordblks;
}

For available memory, I use Module.HEAP8.length from JS. Turns out, at run-time used memory can exceed available memory by a considerable margin, which I do not fully understand:

[Debug] [snapshot-1718799665087] wasm mem = 75366400, used = 84730568 (Δ=0) (main.js, line 39830)

I'm obviously doing something wrong...

sbc100 commented 1 week ago

sbrk() will (almost alwasy) give you the size of the wasm memory and should (almost alwasy) match Module.HEAP8.length.

The only time that sbrk() might be smaller than Module.HEAP8.length is if you grew the wasm memory via some other mechansim other than via sbrk() itself. Since that doesn't normally happen I would just ignore that case and assume that sbrk() always points to the very of the memory.

info.fordblks is the number of bytes in the malloc heap that not free, so I would have though that used memory would be dynamicTop (i.e. the end of heap) minus info.fordblks the number of bytes free the malloc heap

suzukieng commented 1 week ago

Ah... I think I messed up.

size_t getFreeMemory() {
  struct mallinfo i = mallinfo();
  uintptr_t totalMemory = getTotalMemory();
  uintptr_t dynamicTop = (uintptr_t)sbrk(0);
  return totalMemory - dynamicTop + i.fordblks;
}

free = total - used so used should be dynamicTop - i.fordblks (minus instead of plus). Thanks @sbc100 !