Некоторые распределители ленивы?
Я написал программу на C для Linux, которая распределяет память по памяти, запускает ее в цикле, и TOP не показывает никакого потребления памяти.
затем я что-то сделал с этой памятью, и TOP показал потребление памяти.
Когда я использую malloc, действительно ли я "получаю память" или есть "ленивое" управление памятью, которое дает мне память, только если / когда я ее использую?
(Существует также опция, которую TOP знает только о потреблении памяти, когда я ее использую, поэтому я не уверен в этом..)
Спасибо
6 ответов
В Linux malloc запрашивает память с помощью sbrk() или mmap() - так или иначе, ваше адресное пространство немедленно расширяется, но Linux не назначает реальные страницы физической памяти до первой записи на соответствующую страницу. Вы можете увидеть расширение адресного пространства в столбце VIRT, а фактическое использование физической памяти в RES.
Это начинается немного не по теме (а затем я свяжу это с вашим вопросом), но то, что происходит, похоже на то, что происходит, когда вы запускаете процесс в Linux. При разветвлении существует механизм, называемый копировать при записи, который копирует пространство памяти для нового процесса только тогда, когда память записывается. Таким образом, если раздвоенный процесс exec - это новая программа, вы сэкономили накладные расходы на копирование памяти исходных программ.
Возвращаясь к вашему вопросу, идея похожа. Как уже отмечали другие, запрос памяти немедленно возвращает пространство виртуальной памяти, но фактические страницы выделяются только при записи в них.
Какова цель этого? Это в основном делает неправильную память более или менее постоянной операцией Big O(1) вместо операции Big O(n) (аналогично тому, как планировщик Linux распределяет свою работу вместо того, чтобы делать это в одном большом фрагменте).
Чтобы продемонстрировать, что я имею в виду, я выполнил следующий эксперимент:
rbarnes@rbarnes-desktop:~/test_code$ time ./bigmalloc
real 0m0.005s
user 0m0.000s
sys 0m0.004s
rbarnes@rbarnes-desktop:~/test_code$ time ./deadbeef
real 0m0.558s
user 0m0.000s
sys 0m0.492s
rbarnes@rbarnes-desktop:~/test_code$ time ./justwrites
real 0m0.006s
user 0m0.000s
sys 0m0.008s
Программа bigmalloc выделяет 20 миллионов целых, но ничего с ними не делает. deadbeef записывает по одному int на каждую страницу, в результате чего 19531 пишет, а justwrites выделяет 19531 целое и обнуляет их. Как видите, выполнение deadbeef занимает в 100 раз больше времени, чем bigmalloc, и примерно в 50 раз дольше, чем просто сценарии.
#include <stdlib.h>
int main(int argc, char **argv) {
int *big = malloc(sizeof(int)*20000000); // allocate 80 million bytes
return 0;
}
,
#include <stdlib.h>
int main(int argc, char **argv) {
int *big = malloc(sizeof(int)*20000000); // allocate 80 million bytes
// immediately write to each page to simulate all at once allocation
// assuming 4k page size on 32bit machine
for ( int* end = big + 20000000; big < end; big+=1024 ) *big = 0xDEADBEEF ;
return 0;
}
,
#include <stdlib.h>
int main(int argc, char **argv) {
int *big = calloc(sizeof(int),19531); // number of writes
return 0;
}
Да, память не отображается в вашем пространстве памяти, если вы не коснетесь его. При неправильном использовании памяти будут настроены только таблицы подкачки, чтобы они знали, что при возникновении ошибки страницы в выделенной памяти память должна быть сопоставлена.
Эта функция называется overcommit - ядро "обещает" вам память, увеличивая размер сегмента данных, но не выделяет ему физическую память. Когда вы касаетесь адреса в этом новом пространстве, страница процесса сбивается в ядре, которое затем пытается отобразить на нем физические страницы.
Вы используете оптимизацию компилятора? Может быть, оптимизатор удалил распределение, так как вы не используете выделенные ресурсы?
Да, обратите внимание на флаги VirtualAlloc,
MEM_RESERVE
MEM_COMMIT
,
Хех, но для Linux или любой системы POSIX/BSD/SVR# vfork () существует уже давно и предоставляет симулированную функциональность.
Функция vfork () отличается от fork () только тем, что дочерний процесс может делиться кодом и данными с вызывающим процессом (родительским процессом). Это значительно ускоряет клонирование, рискуя нарушить целостность родительского процесса, если vfork () используется не по назначению.
Использование vfork () для любых целей, кроме как в качестве прелюдии к немедленному вызову функции из семейства exec или _exit(), не рекомендуется.
Функция vfork () может использоваться для создания новых процессов без полного копирования адресного пространства старого процесса. Если разветвленный процесс просто вызывает exec, пространство данных, скопированное с родительского на дочерний с помощью fork (), не используется. Это особенно неэффективно в постраничной среде, что делает vfork () особенно полезным. В зависимости от размера родительского пространства данных, vfork () может дать значительное улучшение производительности по сравнению с fork().