При выделении и освобождении блоков памяти произвольного размера с 4 или более потоками с использованием параллельной конструкции openmp для построения программа, похоже, начинает утечку значительных объемов памяти во второй половине тестовой программы ] время выполнения. Таким образом, он увеличивает объем потребляемой памяти с 1050 МБ до 1500 МБ или более без фактического использования дополнительной памяти.
Поскольку valgrind не показывает никаких проблем, я должен предположить, что то, что кажется утечкой памяти, на самом деле является подчеркнутым эффектом памяти фрагментация.
Интересно, что эффект еще не проявляется, если 2 потока выделяют по 10000 выделений каждый, но он сильно проявляется, если 4 потока выделяют по 5000 выделений каждый. Кроме того, если максимальный размер выделенных фрагментов уменьшается до 256 КБ (с 1 МБ), эффект становится слабее.
Может ли тяжелый параллелизм так сильно подчеркивать фрагментацию? Или это, скорее, ошибка в куче?
Демонстрационная программа построена для получения в общей сложности 256 МБ фрагментов памяти произвольного размера из кучи, выполняя 5000 выделений. Если предел памяти достигнут, блоки, выделенные первыми, будут освобождены, пока потребление памяти не упадет ниже предела. После выполнения 5000 распределений вся память освобождается и цикл завершается. Вся эта работа выполняется для каждого потока, сгенерированного openmp.
Эта схема распределения памяти позволяет нам ожидать потребления памяти ~ 260 МБ на поток (включая некоторые бухгалтерские данные).
Как это есть действительно то, что вы, возможно, захотите протестировать, вы можете загрузить пример программы с простым файлом makefile из dropbox .
При запуске программы как есть у вас должно быть не менее 1400 МБ ОЗУ. Не стесняйтесь настраивать константы в коде в соответствии со своими потребностями.
Для полноты фактический код следует:
#include
#include
#include
#include
#include
#include
#include
typedef unsigned long long uint64_t;
void runParallelAllocTest()
{
// constants
const int NUM_ALLOCATIONS = 5000; // alloc's per thread
const int NUM_THREADS = 4; // how many threads?
const int NUM_ITERS = NUM_THREADS;// how many overall repetions
const bool USE_NEW = true; // use new or malloc? , seems to make no difference (as it should)
const bool DEBUG_ALLOCS = false; // debug output
// pre store allocation sizes
const int NUM_PRE_ALLOCS = 20000;
const uint64_t MEM_LIMIT = (1024 * 1024) * 256; // x MB per process
const size_t MAX_CHUNK_SIZE = 1024 * 1024 * 1;
srand(1);
std::vector allocations;
allocations.resize(NUM_PRE_ALLOCS);
for (int i = 0; i < NUM_PRE_ALLOCS; i++) {
allocations[i] = rand() % MAX_CHUNK_SIZE; // use up to x MB chunks
}
#pragma omp parallel num_threads(NUM_THREADS)
#pragma omp for
for (int i = 0; i < NUM_ITERS; ++i) {
uint64_t long totalAllocBytes = 0;
uint64_t currAllocBytes = 0;
std::deque< std::pair > pointers;
const int myId = omp_get_thread_num();
for (int j = 0; j < NUM_ALLOCATIONS; ++j) {
// new allocation
const size_t allocSize = allocations[(myId * 100 + j) % NUM_PRE_ALLOCS ];
char* pnt = NULL;
if (USE_NEW) {
pnt = new char[allocSize];
} else {
pnt = (char*) malloc(allocSize);
}
pointers.push_back(std::make_pair(pnt, allocSize));
totalAllocBytes += allocSize;
currAllocBytes += allocSize;
// fill with values to add "delay"
for (int fill = 0; fill < (int) allocSize; ++fill) {
pnt[fill] = (char)(j % 255);
}
if (DEBUG_ALLOCS) {
std::cout << "Id " << myId << " New alloc " << pointers.size() << ", bytes:" << allocSize << " at " << (uint64_t) pnt << "\n";
}
// free all or just a bit
if (((j % 5) == 0) || (j == (NUM_ALLOCATIONS - 1))) {
int frees = 0;
// keep this much allocated
// last check, free all
uint64_t memLimit = MEM_LIMIT;
if (j == NUM_ALLOCATIONS - 1) {
std::cout << "Id " << myId << " about to release all memory: " << (currAllocBytes / (double)(1024 * 1024)) << " MB" << std::endl;
memLimit = 0;
}
//MEM_LIMIT = 0; // DEBUG
while (pointers.size() > 0 && (currAllocBytes > memLimit)) {
// free one of the first entries to allow previously obtained resources to 'live' longer
currAllocBytes -= pointers.front().second;
char* pnt = pointers.front().first;
// free memory
if (USE_NEW) {
delete[] pnt;
} else {
free(pnt);
}
// update array
pointers.pop_front();
if (DEBUG_ALLOCS) {
std::cout << "Id " << myId << " Free'd " << pointers.size() << " at " << (uint64_t) pnt << "\n";
}
frees++;
}
if (DEBUG_ALLOCS) {
std::cout << "Frees " << frees << ", " << currAllocBytes << "/" << MEM_LIMIT << ", " << totalAllocBytes << "\n";
}
}
} // for each allocation
if (currAllocBytes != 0) {
std::cerr << "Not all free'd!\n";
}
std::cout << "Id " << myId << " done, total alloc'ed " << ((double) totalAllocBytes / (double)(1024 * 1024)) << "MB \n";
} // for each iteration
exit(1);
}
int main(int argc, char** argv)
{
runParallelAllocTest();
return 0;
}
Насколько я понимаю, оборудование имеет большое значение. Тест может потребовать корректировки, если он выполняется на более быстрой машине.
Intel(R) Core(TM)2 Duo CPU T7300 @ 2.00GHz
Ubuntu 10.04 LTS 64 bit
gcc 4.3, 4.4, 4.6
3988.62 Bogomips
После того, как вы запустите make-файл, вы должны получить файл с именем ompmemtest
. Чтобы запросить использование памяти с течением времени, я использовал следующие команды:
./ompmemtest &
top -b | grep ompmemtest
Что дает весьма впечатляющую фрагментацию или поведение утечки. Ожидаемое потребление памяти с 4 потоками составляет 1090 МБ, что со временем стало 1500 МБ:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
11626 byron 20 0 204m 99m 1000 R 27 2.5 0:00.81 ompmemtest
11626 byron 20 0 992m 832m 1004 R 195 21.0 0:06.69 ompmemtest
11626 byron 20 0 1118m 1.0g 1004 R 189 26.1 0:12.40 ompmemtest
11626 byron 20 0 1218m 1.0g 1004 R 190 27.1 0:18.13 ompmemtest
11626 byron 20 0 1282m 1.1g 1004 R 195 29.6 0:24.06 ompmemtest
11626 byron 20 0 1471m 1.3g 1004 R 195 33.5 0:29.96 ompmemtest
11626 byron 20 0 1469m 1.3g 1004 R 194 33.5 0:35.85 ompmemtest
11626 byron 20 0 1469m 1.3g 1004 R 195 33.6 0:41.75 ompmemtest
11626 byron 20 0 1636m 1.5g 1004 R 194 37.8 0:47.62 ompmemtest
11626 byron 20 0 1660m 1.5g 1004 R 195 38.0 0:53.54 ompmemtest
11626 byron 20 0 1669m 1.5g 1004 R 195 38.2 0:59.45 ompmemtest
11626 byron 20 0 1664m 1.5g 1004 R 194 38.1 1:05.32 ompmemtest
11626 byron 20 0 1724m 1.5g 1004 R 195 40.0 1:11.21 ompmemtest
11626 byron 20 0 1724m 1.6g 1140 S 193 40.1 1:17.07 ompmemtest
Обратите внимание: Я мог воспроизвести эту проблему при компиляции с gcc 4.3, 4. 4 и 4.6 (ствол) .