Почему malloc в WebAssembly требует 4x памяти?
Я написал скрипт на C для выделения памяти с помощью malloc() по бесконечному циклу. Моей целью было реализовать простой отказ в обслуживании с помощью WebAssembly, открыв несколько вкладок и вызвав сбой браузера. Я могу выделить не более 2 ГБ для каждой вкладки, чтобы предотвратить ее падение (ограничение памяти для браузеров x64).
#include <stdlib.h>
#define MAX_MEM 2147483630 //2 GB
int main() {
long int mem_used=209715000;
while(1){
if(mem_used<MAX_MEM){
int *ptr = malloc(sizeof(int));
mem_used+=4;
}
}
return 0;
}
Я ожидал, что это сработает, но вместо этого вкладка вылетает. Из тестов, которые я сделал, mem_used+=16 - правильный выбор, чтобы предотвратить падение вкладки. Я не очень хорошо разбираюсь в управлении памятью WebAssembly, поэтому я подумал, что, возможно, для этого требуется в 4 раза больше памяти. Это верно?
3 ответа
В emscripten malloc добавляет некоторый минимальный размер чанка, а затем выравнивает адрес по крайней мере до 8 байтовых границ. Таким образом, для небольших распределений (даже нулевых байтов), malloc будет занимать значительно больше места, чем необходимо. Для больших распределений накладные расходы будут относительно небольшими.
Смотрите комментарии в dlmalloc.c.
Следующая программа демонстрирует, сколько места занимает malloc:
#include <iostream>
int main() {
char *previous, *current;
previous = (char*)malloc(0);
for(int i=0; i<32; ++i) {
current = (char*)malloc(i+1);
std::cout << "malloc(" << i << ") consumed " << (current-previous) << " bytes\n";
previous = current;
}
std::cout << "\n";
previous = (char*)malloc(1);
for(int i=0; i<12; ++i) {
current = (char*)malloc( 1<<(i+1) );
std::cout << "malloc(" << (1<<i) << ") consumed " << (current-previous) << " bytes\n";
previous = current;
}
return 0;
}
Это дает следующий вывод:
malloc(0) consumed 16 bytes
malloc(1) consumed 16 bytes
malloc(2) consumed 16 bytes
malloc(3) consumed 16 bytes
malloc(4) consumed 16 bytes
malloc(5) consumed 16 bytes
malloc(6) consumed 16 bytes
malloc(7) consumed 16 bytes
malloc(8) consumed 16 bytes
malloc(9) consumed 16 bytes
malloc(10) consumed 16 bytes
malloc(11) consumed 16 bytes
malloc(12) consumed 16 bytes
malloc(13) consumed 24 bytes
malloc(14) consumed 24 bytes
malloc(15) consumed 24 bytes
malloc(16) consumed 24 bytes
malloc(17) consumed 24 bytes
malloc(18) consumed 24 bytes
malloc(19) consumed 24 bytes
malloc(20) consumed 24 bytes
malloc(21) consumed 32 bytes
malloc(22) consumed 32 bytes
malloc(23) consumed 32 bytes
malloc(24) consumed 32 bytes
malloc(25) consumed 32 bytes
malloc(26) consumed 32 bytes
malloc(27) consumed 32 bytes
malloc(28) consumed 32 bytes
malloc(29) consumed 40 bytes
malloc(30) consumed 40 bytes
malloc(31) consumed 40 bytes
malloc(1) consumed 16 bytes
malloc(2) consumed 16 bytes
malloc(4) consumed 16 bytes
malloc(8) consumed 16 bytes
malloc(16) consumed 24 bytes
malloc(32) consumed 40 bytes
malloc(64) consumed 72 bytes
malloc(128) consumed 136 bytes
malloc(256) consumed 264 bytes
malloc(512) consumed 520 bytes
malloc(1024) consumed 1032 bytes
malloc(2048) consumed 2056 bytes
Смотрите полный исходный код в этом репо
Ваша проблема в том, что реализации malloc обычно:
a) Include overhead; and
b) Round up to some unit
malloc (sizeof(int)) использует больше, чем sizeof(int) байтов за кулисами.
В любой системе malloc()
всегда немного использует больше памяти, чем вы запрашиваете. Emscripten использует dlmalloc
популярный malloc()
реализация, по умолчанию. Согласно Википедии:
Память в куче выделяется как "чанки", 8-байтовая выровненная структура данных, которая содержит заголовок и используемую память. Выделенная память содержит 8 или 16-байтовые служебные данные для размера фрагмента и флагов использования. Нераспределенные чанки также хранят указатели на другие свободные чанки в области полезного пространства, делая минимальный размер чанка 16 байтов (32-битная система) и 24 байта (64-битная система).
Это означает, что даже один байт выделил блок памяти malloc(1)
использует не менее 16 байтов до 24 байтов. Это связано с тем, что проблема с выравниванием памяти и каждому выделенному блоку требуются дополнительные байты для хранения метаданных блока. Вы можете легко гуглить, как malloc()
работает, чтобы понять, почему есть такие накладные расходы.
Следовательно, для достижения вашей цели тест должен выделять намного больший блок памяти на каждой итерации, чтобы минимизировать такие издержки. Я бы лично порекомендовал 4kb или 1MB вместо sizeof(int).