Выше, чем ожидалось, использование памяти с VirtualAlloc; в чем дело?
Важно: прокрутите вниз до "окончательного обновления", прежде чем тратить здесь слишком много времени. Оказывается, главный урок - остерегаться побочных эффектов других тестов в вашем наборе юнит-тестов и всегда воспроизводить вещи изолированно, прежде чем делать выводы!
На первый взгляд, следующий 64-битный код выделяет (и осуществляет доступ) мегабайты по 4 Мб с использованием VirtualAlloc (всего 4 Гбайт):
const size_t N=4; // Tests with this many Gigabytes
const size_t pagesize4k=4096;
const size_t npages=(N<<30)/pagesize4k;
BOOST_AUTO_TEST_CASE(test_VirtualAlloc) {
std::vector<void*> pages(npages,0);
for (size_t i=0;i<pages.size();++i) {
pages[i]=VirtualAlloc(0,pagesize4k,MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE);
*reinterpret_cast<char*>(pages[i])=1;
}
// Check all allocs succeeded
BOOST_CHECK(std::find(pages.begin(),pages.end(),nullptr)==pages.end());
// Free what we allocated
bool trouble=false;
for (size_t i=0;i<pages.size();++i) {
const BOOL err=VirtualFree(pages[i],0,MEM_RELEASE);
if (err==0) trouble=true;
}
BOOST_CHECK(!trouble);
}
Однако при его выполнении "Рабочий набор" увеличивается в диспетчере задач Windows (и подтверждается значением "прилипания" в столбце "Пиковый рабочий набор") с базовой линии ~200 000 КБ (~200 МБайт) до более 6 000 000 или 7 000 000 КБ. (протестировано на 64-битной Windows7, а также на 64-битной Server 2003 и Server 2008 с виртуализацией ESX; к сожалению, я не обратил внимание на то, на каких системах наблюдалось различное число).
Еще один очень похожий тестовый пример в том же исполняемом модульном тесте - тесты one-mega 4k malloc (за которыми следуют освобождения), который расширяется только на ожидаемые 4 Гбайт при работе.
Я не понимаю: VirtualAlloc имеет довольно высокие накладные расходы на выделение ресурсов? Это явно значительная часть размера страницы, если так; зачем так много лишнего и для чего это нужно? Или я неправильно понимаю, что на самом деле означает "рабочий набор"? Что тут происходит?
Обновление: со ссылкой на ответ Ганса, я отмечаю, что это не удается с нарушением прав доступа при доступе ко второй странице, поэтому все, что происходит, не так просто, как выделение, округленное до "гранулярности" 64 КБ.
char*const ptr = reinterpret_cast<char*>(
VirtualAlloc(0, 4096, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)
);
ptr[0] = 1;
ptr[4096] = 1;
Обновление: теперь на экземпляре AWS/EC2 Windows2008 R2, с установленным VisualStudioExpress2013, я не могу воспроизвести проблему с этим минимальным кодом (скомпилированным 64-битным), который завершается рабочим набором пиков без лишних накладных расходов, равным 4335816 КБ, что составляет номер, который я ожидал увидеть изначально. Так что либо в других машинах, на которых я работаю, есть что-то иное, либо в исполняемом тесте, основанном на буст-тесте, который использовался в предыдущем тестировании. Биззаро, продолжение следует...
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <vector>
int main(int, char**) {
const size_t N = 4;
const size_t pagesize4k = 4096;
const size_t npages = (N << 30) / pagesize4k;
std::vector<void*> pages(npages, 0);
for (size_t i = 0; i < pages.size(); ++i) {
pages[i] = VirtualAlloc(0, pagesize4k, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
*reinterpret_cast<char*>(pages[i]) = 1;
}
Sleep(5000);
for (size_t i = 0; i < pages.size(); ++i) {
VirtualFree(pages[i], 0, MEM_RELEASE);
}
return 0;
}
Окончательное обновление: извинения! Я бы удалил этот вопрос, если бы мог, потому что оказалось, что наблюдаемые проблемы были полностью связаны с непосредственным предшествующим юнит-тестом в наборе тестов, который использовал "масштабируемый распределитель" TBB для выделения / освобождения пары гигабайт вещей. Кажется, масштабируемый распределитель фактически сохраняет такие выделения в своем собственном пуле, а не возвращает их в систему (см., Например, здесь или здесь). Стало очевидным, как только я провел тесты индивидуально с достаточным количеством Sleep
после того, как они наблюдают за своим рабочим набором по завершении в диспетчере задач (может ли быть что-нибудь предпринято с поведением TBB, может быть интересным вопросом, но как-то вопрос здесь - красная сельдь).
2 ответа
Оказывается, что наблюдаемые проблемы были полностью связаны с непосредственным предшествующим юнит-тестом в наборе тестов, который использовал "масштабируемый распределитель" TBB для выделения / освобождения пары гигабайт содержимого. Кажется, масштабируемый распределитель фактически сохраняет такие выделения в своем собственном пуле, а не возвращает их в систему (см., Например, здесь или здесь). Стало очевидным, как только я провел тесты индивидуально с достаточным количеством Sleep
после них наблюдать за выполнением рабочего набора в диспетчере задач.
pages[i]=VirtualAlloc(0,pagesize4k,MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE);
Вы не получите 4096 байт, оно будет округлено до минимально допустимого распределения. Это SYSTEM_INFO.dwAllocationGranularity, это было 64 КБ в течение длительного времени. Это очень простая мера фрагментации адресного пространства.
Таким образом, вы выделяете гораздо больше, чем вы думаете.