glibc中重复内存分配的效率

时间:2009-06-07 16:06:24

标签: c performance memory-management glibc

以下是来自着名的LAPACK数值库的Fortran ZHEEVR例程的C包装器:

void zheevr(char jobz, char range, char uplo, int n, doublecomplex* a, int lda, double vl, double vu, int il, int iu, double abstol, double* w, doublecomplex* z, int ldz, int* info)
{
    int m;
    int lwork = -1;
    int liwork = -1;
    int lrwork = -1;
    int* isuppz = alloc_memory(sizeof(int) * 2 * n);
    zheevr_(&jobz, &range, &uplo, &n, a, &lda, &vl, &vu, &il, &iu, &abstol, &m, w, z, &ldz, isuppz, small_work_doublecomplex, &lwork, small_work_double, &lrwork, small_work_int, &liwork, &info);
    lwork = (int) small_work_doublecomplex[0].real;
    liwork = small_work_int[0];
    lrwork = (int) small_work_double[0];
    doublecomplex* work = alloc_memory(sizeof(doublecomplex) * lwork);
    double* rwork = alloc_memory(sizeof(double) * lrwork);
    int* iwork = alloc_memory(sizeof(int) * liwork);
    zheevr_(&jobz, &range, &uplo, &n, a, &lda, &vl, &vu, &il, &iu, &abstol, &m, w, z, &ldz, isuppz, work, &lwork, rwork, &lrwork, iwork, &liwork, info);
    free(iwork);
    free(rwork);
    free(work);
    free(isuppz);
}

在我的应用程序中,这个函数被称为数十万次,以对复杂矩阵“a”(参数名称遵循此函数的Fortran约定)进行对角化,以获得相同的矩阵大小。我认为工作数组大小在大多数时候都是相同的,因为对角化矩阵将具有相同的结构。我的问题是:

  1. 重复的alloc / free(“alloc_memory”是一个围绕glibc的malloc的简单包装)可以调用会损害性能,还有多么糟糕?
  2. 免费的顺序是否重要?我应该先释放最后一个分配的数组,还是最后一个?

5 个答案:

答案 0 :(得分:5)

1)是的,他们可以。

2)任何理智的libc都不应该担心free()的顺序。表现明智也不重要。

我建议从此函数中删除内存管理 - 因此调用者将提供矩阵大小和分配的临时缓冲区。如果从相同大小的矩阵上的相同位置调用此函数,那将大大减少malloc的数量。

答案 1 :(得分:5)

  • 你能用C99吗? (答案:是的,你已经在使用C99表示法 - 在需要时声明变量。)
  • 阵列的大小是否合理(不是太大)?

如果两个答案都是“是”,请考虑使用VLA - 可变长度数组:

void zheevr(char jobz, char range, char uplo, int n, doublecomplex* a, int lda, double vl, double vu, int il, int iu, double abstol, double* w, doublecomplex* z, int ldz, int* info)
{
    int m;
    int lwork = -1;
    int liwork = -1;
    int lrwork = -1;
    int isuppz[2*n];
    zheevr_(&jobz, &range, &uplo, &n, a, &lda, &vl, &vu, &il, &iu, &abstol, &m, w, z, &ldz, isuppz, small_work_doublecomplex, &lwork, small_work_double, &lrwork, small_work_int, &liwork, &info);
    lwork = (int) small_work_doublecomplex[0].real;
    liwork = small_work_int[0];
    lrwork = (int) small_work_double[0];
    doublecomplex work[lwork];
    double rwork[lrwork];
    int iwork[liwork];
    zheevr_(&jobz, &range, &uplo, &n, a, &lda, &vl, &vu, &il, &iu, &abstol, &m, w, z, &ldz, isuppz, work, &lwork, rwork, &lrwork, iwork, &liwork, info);
}

使用VLA的一个好处是,您无需自由完成。

(未经测试的代码!)

答案 2 :(得分:2)

这肯定会影响性能 - 你只能通过计时找到多少。要创建一个避免大多数分配的版本,请分配给静态指针并记住另一个静态整数的大小。如果下一个调用使用相同的大小,只需重用上次分配的内容即可。只有在需要创建新矩阵时才释放任何内容,因为大小已经改变。

请注意,此解决方案仅适用于单线程代码。

答案 3 :(得分:1)

好的。您很快就会得到探查器的答案。如果您有AMD机器,我强烈推荐免费的AMD CodeAnalyst。

至于你的记忆问题,我认为在这种情况下你可以使用本地内存管理。只需确定可为此功能分配的最大内存数。 接下来,您声明一个静态缓冲区,您可以使用它,就像编译器处理堆栈一样。我曾经在VirtualAlloc上做了一次这样的包装,而且非常快。

答案 4 :(得分:1)

如果要分配相同大小的项目数十万次,那么为什么不只是维护一堆对象(因为这些似乎相对简单,即不包含指向其他已分配内存的指针)并且自由进行你自己的堆(或实际堆栈)?

堆可以使用glib malloc懒惰地分配新对象,但是当释放时只需将项目推送到堆上。当你需要分配时,如果有一个可用的释放对象,它可以只分配那个。

这也将为您节省多次分配调用(因为您不需要进行任何分配,看起来您的例程会对malloc进行多次调用),并且至少在re上也会避免碎片(在某种程度上) - 使用的记忆。当然,初始分配(以及程序在需要扩展此内存时运行的其他分配)可能会导致碎片,但如果您真的担心这一点,您可以运行一些统计数据并找到您的平均/最大/典型大小运行期间堆,并在程序启动时立即预分配,避免碎片。