Утечка памяти при использовании OpenMP

В приведенном ниже тестовом примере не хватает памяти на 32-битных машинах (выбрасывается std::bad_alloc) в цикле, следующем за сообщением «post MT section» при использовании OpenMP, однако, если #pragmas для OpenMP закомментированы, код выполняется до завершения в порядке, поэтому кажется, что когда память выделяется в параллельных потоках, она не освобождается правильно, и поэтому у нас заканчивается память.

Вопрос в том, что-то не так с кодом выделения и удаления памяти, приведенным ниже, или это ошибка в gcc v4.2.2 или OpenMP? Я также попробовал gcc v4.3 и получил такой же сбой.

int main(int argc, char** argv)
{
    std::cout << "start " << std::endl;

    {
            std::vector<std::vector<int*> > nts(100);
            #pragma omp parallel
            {
                    #pragma omp for
                    for(int begin = 0; begin < int(nts.size()); ++begin) {
                            for(int i = 0; i < 1000000; ++i) {
                                    nts[begin].push_back(new int(5));
                            }
                    }
            }

    std::cout << "  pre delete " << std::endl;
            for(int begin = 0; begin < int(nts.size()); ++begin) {
                    for(int j = 0; j < nts[begin].size(); ++j) {
                            delete nts[begin][j];
                    }
            }
    }
    std::cout << "post MT section" << std::endl;
    {
            std::vector<std::vector<int*> > nts(100);
            int begin, i;
            try {
              for(begin = 0; begin < int(nts.size()); ++begin) {
                    for(i = 0; i < 2000000; ++i) {
                            nts[begin].push_back(new int(5));
                    }
              }
            } catch (std::bad_alloc &e) {
                    std::cout << e.what() << std::endl;
                    std::cout << "begin: " << begin << " i: " << i << std::endl;
                    throw;
            }
            std::cout << "pre delete 1" << std::endl;

            for(int begin = 0; begin < int(nts.size()); ++begin) {
                    for(int j = 0; j < nts[begin].size(); ++j) {
                            delete nts[begin][j];
                    }
            }
    }

    std::cout << "end of prog" << std::endl;

    char c;
    std::cin >> c;

    return 0;
}

person WilliamKF    schedule 02.12.2010    source источник
comment
Когда я запускаю это под Windows, созданной с помощью компилятора Intel, мои выделения начинают давать сбой в первом цикле из-за достижения предела 2 ГБ для 32-битного процесса. Возможно ли, что накладные расходы OpenMP просто подталкивают ваш процесс к пределу, установленному на вашей платформе?   -  person Scott    schedule 02.12.2010
comment
@Scott Danahy Попробуйте изменить тестовый пример, чтобы сократить все выделения вдвое, этот тест работал с ограничением в 4 ГБ.   -  person WilliamKF    schedule 02.12.2010


Ответы (3)


Изменение первого цикла OpenMP с 1000000 на 2000000 вызовет ту же ошибку. Это указывает на то, что проблема нехватки памяти связана с ограничением стека OpenMP.

Попробуйте установить ограничение стека OpenMP на неограниченный в bash с помощью

ulimit -s unlimited

Вы также можете изменить переменную среды OpenMP OMP_STACKSIZE и установить для нее значение 100 МБ или более.

ОБНОВЛЕНИЕ 1: я меняю первый цикл на

{
    std::vector<std::vector<int*> > nts(100);
    #pragma omp for schedule(static) ordered
    for(int begin = 0; begin < int(nts.size()); ++begin) {
        for(int i = 0; i < 2000000; ++i) {
            nts[begin].push_back(new int(5));
        }
    }

    std::cout << "  pre delete " << std::endl;
    for(int begin = 0; begin < int(nts.size()); ++begin) {
        for(int j = 0; j < nts[begin].size(); ++j) {
            delete nts[begin][j]
        }
    }
}

Затем я получаю ошибку памяти по адресу i=1574803 в основном потоке.

ОБНОВЛЕНИЕ 2: Если вы используете компилятор Intel, вы можете добавить следующее в начало своего кода, и это решит проблему (при условии, что у вас достаточно памяти для дополнительных накладных расходов).

std::cout << "Previous stack size " << kmp_get_stacksize_s() << std::endl;
kmp_set_stacksize_s(1000000000);
std::cout << "Now stack size " << kmp_get_stacksize_s() << std::endl;

ОБНОВЛЕНИЕ 3: Для полноты картины, как упоминал другой участник, если вы выполняете какие-то числовые вычисления, лучше всего предварительно выделить все в одном новом поплавке[1000000] вместо того, чтобы использовать OpenMP для выполнения 1000000 распределений. Это относится и к распределению объектов.

person Dat Chu    schedule 02.12.2010
comment
Когда вы изменяете первый цикл на 2000000, каков общий выделенный размер для процесса, когда ему не удается выделить новую память? - person Scott; 02.12.2010
comment
i=1574803 при сбое. Смотрите мое ОБНОВЛЕНИЕ 1. - person Dat Chu; 02.12.2010
comment
@Dat Chu Итак, без использования компилятора Intel (т.е. с использованием gcc) параметр env var OMP_STACKSIZE — это правильный путь, потому что kmp_set_stacksize_s () недоступен в gcc? - person WilliamKF; 02.12.2010
comment
Да. Убедитесь, что вы либо указываете значения в КБ, либо указываете правильный суффикс gcc.gnu.org /onlinedocs/libgomp/OMP_005fSTACKSIZE.html - person Dat Chu; 02.12.2010
comment
@DatChu Я пытался работать с OMP_STACKSIZE=100M, а также пробовал ulimit, но он все равно не работает с begin=76 и i=1048576 в обновленном примере кода в вопросе, но если OpenMP не используется, код завершается без нехватки памяти . - person WilliamKF; 03.12.2010
comment
@DatChu Почему прагма меняется с моего примера на «#pragma omp для расписания (статического) заказано»? - person WilliamKF; 03.12.2010
comment
@WilliamKF: у меня нет доступа к машине с Linux с оперативной памятью менее 8 ГБ, но я попытаюсь найти ее, чтобы протестировать ее под Linux. Re: изменение прагмы, мне легче отлаживать, если это упорядоченное статическое планирование. Вы можете просто использовать #pragma omp parallel для - person Dat Chu; 03.12.2010
comment
@DatChu Просто создайте -m32 для 32-битного исполняемого файла, и у вас закончится память на машине с 8 ГБ. - person WilliamKF; 07.12.2010

Я нашел эту проблему в другом месте без OpenMP, но только с использованием pthreads. Дополнительное потребление памяти при многопоточности является типичным поведением стандартного распределителя памяти. При переключении на распределитель Hoard дополнительное потребление памяти исчезает.

person WilliamKF    schedule 03.12.2010

Почему вы используете int* в качестве внутреннего члена вектора? Это очень расточительно - у вас есть 4 байта (sizeof(int), строго) данных и в 2-3 раза больше структуры управления кучей для каждой vector записи. Попробуйте это, просто используя vector<int>, и посмотрите, работает ли он лучше.

Я не специалист по OpenMP, но такое использование кажется странным в своей асимметрии - вы заполняете векторы в параллельной секции и очищаете их в непараллельном коде. Не могу сказать, неправильно ли это, но это «кажется» неправильным.

person Steve Townsend    schedule 02.12.2010
comment
@Steve Townsend Этот тестовый пример предназначен для демонстрации проблемы, а не для фактического использования кода. Массив из пяти целых чисел в каждой записи помогает использовать память для демонстрации утечки. Если сделать код более эффективным с точки зрения использования памяти, то сбой будет отложен до тех пор, пока не будет использовано еще больше памяти. - person WilliamKF; 02.12.2010
comment
@WilliamKF - Понятно. Я думаю, вам нужен гуру OpenMP, чтобы прокомментировать. Вопрос касается любых проблем с выделением и удалением памяти, и мне показалось, что RAII будет предпочтительнее. Я добавляю +1 к вашему вопросу, так как мне самому интересен ответ. - person Steve Townsend; 02.12.2010
comment
Поскольку код предназначен для демонстрации проблемы, он кажется немного неуклюжим, но нередко очистка и постобработка выполняются вне конструкций OpenMP. - person Dat Chu; 02.12.2010
comment
@Steve Townsend Я думаю, что ключевым моментом здесь является то, что отключение OpenMP приводит к тому, что в тестовом примере не заканчивается память. - person WilliamKF; 02.12.2010
comment
Да, это то, что я задавался вопросом, проблематична ли очистка в непараллельном коде. Другая проблема может заключаться в вложенности циклов без каких-либо частных переменных потока. - person Steve Townsend; 02.12.2010
comment
@Steve: OpenMP автоматически сделает переменные во внутренней области потокозависимыми. Проблема, указанная в ответе Дэна, вероятно, является настоящей причиной. - person Konrad Rudolph; 02.12.2010
comment
@Конрад - спасибо, я уверен, что это так. Позор, я надеялся узнать здесь немного магии OpenMP. - person Steve Townsend; 02.12.2010
comment
@Steve: в OpenMP очень мало магии. Плохо то… что магии очень мало. OpenMP очень примитивен во многих отношениях. Он ведет себя очень привитым. - person Konrad Rudolph; 02.12.2010
comment
@SteveTownsend По-видимому, это ошибка в malloc () / free () gcc, поскольку я вижу проблему без OpenMP, а только с posix pthreads, и переключение malloc на альтернативу, такое хранилище решает проблему, как я писал в другом ответе. - person WilliamKF; 07.12.2010