Почему malloc в WebAssembly требует 4x памяти? - PullRequest
1 голос
/ 07 мая 2019

Я написал скрипт на 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 ]

0 голосов
/ 08 мая 2019

В 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

См. Полный исходный код в этом репо

0 голосов
/ 08 мая 2019

В любой системе malloc() всегда немного использует больше памяти, чем вы запрашиваете. Emscripten использует dlmalloc, популярную реализацию malloc(), по умолчанию. Согласно Википедии:

Память в куче выделяется как «чанки», 8-байтовая выровненная структура данных, которая содержит заголовок и используемую память. Выделенная память содержит 8 или 16-байтовые служебные данные для размера фрагмента и флагов использования. Нераспределенные чанки также хранят указатели на другие свободные чанки в области полезного пространства, делая минимальный размер чанка 16 байт (32-разрядная система) и 24 байта (64-разрядная система) .

Это означает, что даже выделенный блок памяти из одного байта malloc(1) использует по крайней мере от 16 до 24 байтов. Это связано с тем, что проблема с выравниванием памяти и каждому выделенному блоку требуются дополнительные байты для хранения метаданных блока. Вы можете легко узнать, как работает malloc(), чтобы понять, почему возникают такие издержки.

Поэтому, чтобы удовлетворить ваши цели, тест должен выделять гораздо больший блок памяти на каждой итерации, чтобы минимизировать такие издержки. Я бы лично порекомендовал 4КБ или 1МБ вместо sizeof (int).

0 голосов
/ 07 мая 2019

Ваша проблема в том, что реализации malloc обычно:

a) Include overhead; and 
b) Round up to some unit

malloc (sizeof (int)) использует больше, чем sizeof (int) байтов за кулисами.

...