使用OpenMP时出现内存泄漏问题

6
以下测试用例在使用OpenMP时会在“post MT section”消息后的循环中耗尽32位机器上的内存(抛出std :: bad_alloc),但是,如果注释掉OpenMP的#pragma,则代码可以正常完成运行,因此似乎在并行线程中分配内存时未正确释放,导致我们耗尽内存。

问题是下面的内存分配和删除代码是否有问题,还是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;
}

当我在使用Intel编译器构建的Windows上运行此程序时,由于32位进程的2GB限制,我的分配在第一次循环中开始失败。是否有可能OpenMP的开销正好将您的进程推到平台上的任何限制之上? - Scott
@Scott Danahy 请尝试将测试用例更改为将所有分配减半,这个测试在4 GB限制下有效。 - WilliamKF
3个回答

3

我在其他地方发现了这个问题,没有使用OpenMP,而是使用pthread。多线程时额外的内存消耗似乎是标准内存分配器的典型行为。通过切换到Hoard分配器,额外的内存消耗就会消失。


3

将第一个OpenMP循环从1000000更改为2000000将导致相同的错误。这表明内存不足的问题出现在OpenMP堆栈限制上。

尝试在bash中将OpenMP堆栈限制设置为无限制:

ulimit -s unlimited

你可以通过更改OpenMP环境变量OMP_STACKSIZE并将其设置为100MB或更多来实现目的。
更新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:如果您正在使用英特尔编译器,您可以在代码顶部添加以下内容,它将解决该问题(前提是您有足够的内存来处理额外的开销)。

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:为了完整起见,正如另一位成员所提到的,如果您正在执行一些数值计算,最好预先分配一个 new float[1000000],而不是使用 OpenMP 进行 1000000 次分配。这同样适用于分配对象。


当您将第一个循环更改为2000000时,当无法分配新内存时,该进程的总分配大小是多少? - Scott
当它崩溃时,i=1574803。请查看我的更新1。 - Dat Chu
@DatChu 为什么我的示例中的pragma改为“#pragma omp for schedule(static) ordered”? - WilliamKF
@WilliamKF:我没有可以访问的Linux机器,其内存小于8GB,但我会尝试找到一台这样的机器,以便在Linux下进行测试。关于#pragma change,如果采用有序静态调度,那么对我来说更容易调试。你可以简单地使用#pragma omp parallel for。 - Dat Chu
@DatChu 只需使用-m32来构建32位可执行文件,您就会在具有8GB内存的机器上耗尽内存。 - WilliamKF
显示剩余3条评论

0

为什么要使用int*作为内部向量成员?这非常浪费——每个vector条目都有4字节(sizeof(int))的数据和2-3倍的堆控制结构。尝试只使用vector<int>,看看是否运行得更好。

我不是OpenMP专家,但这种用法在其不对称性方面似乎很奇怪——你在并行部分填充向量,在非并行代码中清除它们。不能告诉你这是否错误,但感觉上是错的。


@Steve Townsend 这个测试案例旨在展示问题,并不是实际使用的代码。每个条目中的五个整数数组有助于消耗内存来演示泄漏。使代码更具内存效率只会导致故障被延迟,直到消耗了更多的内存。 - WilliamKF
@WilliamKF - 我明白了。我认为你需要一位OpenMP大师来发表评论。问题询问有关内存分配和删除的任何问题,对我而言,RAII似乎更可取。我会给你的问题加上+1,因为我也对答案感兴趣。 - Steve Townsend
是的,我想知道在非并行代码中进行清理是否有问题。另一个问题可能是循环嵌套没有任何线程专用变量。 - Steve Townsend
@Steve:OpenMP会自动将内部作用域中的变量设为线程私有。在Dan的回答中指出的问题可能是真正的原因。 - Konrad Rudolph
@Konrad - 谢谢,我确定就是这样。真遗憾,我本来希望在这里学习一些OpenMP技巧的。 - Steve Townsend
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接