Выше, чем ожидалось использование памяти с VirtualAlloc; что происходит?


Важно : прокрутите вниз до "окончательного обновления", прежде чем тратить здесь слишком много времени. Оказывается, главный урок состоит в том, чтобы остерегаться побочных эффектов других тестов в вашем наборе unittest и всегда воспроизводить вещи изолированно , Прежде чем делать поспешные выводы!


На первый взгляд, следующий 64-битный код выделяет (и обращается) к одной мега-4K страницам с помощью 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 K (~200 Мбайт) до более чем 6 000 000 или 7 000 000 K (тестируется на 64-битном Windows7, а также на ESX-виртуализированном 64-битном сервере 2003 и Server 2008; к сожалению, я не обратил внимания, на каких системах наблюдались различные числа).

Еще один очень похожий тестовый случай в тех же unittest исполняемых тестах one-mega 4k mallocs (далее освобождает), и это только расширяется примерно на ожидаемые 4 Гбайт при запуске.

Я не понимаю: у VirtualAlloc есть какие-то довольно высокие накладные расходы на аллок? Очевидно, что это значительная часть размера страницы, если это так; зачем нужно так много дополнительного и для чего это нужно? Или я неправильно понимаю, что на самом деле означает" рабочий набор"? Что здесь происходит?

Update : со ссылкой на ответ Ганса, я отмечаю, что это не удается с нарушением доступа на второй странице доступа, так что все, что происходит, не так просто, как распределение, округленное до 64K "детализации".

char*const ptr = reinterpret_cast<char*>(
  VirtualAlloc(0, 4096, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)
);
ptr[0] = 1;
ptr[4096] = 1;

Update : Теперь на экземпляре AWS / EC2 Windows2008 R2 с установленным VisualStudioExpress2013 я не могу воспроизвести проблему с этим минимальным кодом (скомпилированным 64-битным), который завершается, по-видимому, свободным от накладных расходов пиковым рабочим набором 4,335,816 K, который я ожидал увидеть изначально. Так что либо есть что-то другое в других машинах, которые я запуск На, или Boost-test на основе exe, используемого в предыдущем тестировании. Биззаро, Продолжение следует...

#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;
}

Последнее обновление : извинения! Я бы удалил этот вопрос, если бы мог, потому что оказалось, что наблюдаемые проблемы были полностью из-за непосредственно предшествующего unittest в тестовом наборе, который использовал "масштабируемый распределитель" TBB, чтобы выделить/освободить пару Гбайт материала. Похоже, что масштабируемый распределитель фактически сохраняет такие распределения в своем собственном пуле вместо того, чтобы возвращать их в систему (см., например, здесь или здесь). Стало очевидным, как только я запустил тесты индивидуально с достаточным количеством Sleep после них, чтобы наблюдать их рабочий набор по завершению в диспетчере задач (можно ли что-нибудь сделать с поведением TBB, может быть, интересный вопрос, но как-это вопрос здесь является отвлекающим маневром).

2 4

2 ответа:

   pages[i]=VirtualAlloc(0,pagesize4k,MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE);

Вы не получите 4096 байт,это будет округлено до наименьшего разрешенного выделения. Это SYSTEM_INFO.dwAllocationGranularity, он был 64KB в течение длительного времени. Это очень простой счетчик фрагментации адресного пространства.

Таким образом, вы выделяете Путь больше, чем вы думаете.

Оказывается, что наблюдаемые проблемы былиполностью из-за непосредственно предшествующего unittest в тестовом наборе, который использовал "масштабируемый распределитель" TBB, чтобы выделить/освободить пару Гбайт материала. Похоже, что масштабируемый распределитель фактически сохраняет такие распределения в своем собственном пуле, а не возвращает их в систему (см., например, здесь или здесь). Стало очевидным, как только я провел тесты индивидуально с достаточным количеством Sleep после них, чтобы наблюдать их работу по завершению установите в диспетчере задач.