Подчеркивает ли многопоточность фрагментацию памяти?

Описание

При выделении и освобождении блоков памяти произвольного размера с 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 (ствол) .

38
задан Byron 4 May 2011 в 11:37
поделиться