1

下面是我对来自著名 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. 免费的顺序重要吗?我应该先释放最后分配的数组,还是最后释放?
4

5 回答 5

5

1)是的,他们可以。

2) 任何理智的 libc 都不应该担心 free() 的顺序。性能方面也应该无关紧要。

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

于 2009-06-07T16:17:35.590 回答
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 的一个好处是您无需做任何事情。

(未经测试的代码!)

于 2009-06-07T16:48:21.863 回答
2

它肯定会影响性能-youb 只能通过时间确定确定。要创建避免大多数分配的版本,请分配给静态指针并记住另一个静态整数的大小。如果下一次调用使用相同的大小,只需重用上次分配的内容。仅当您需要创建新矩阵时才释放任何东西,因为大小已更改。

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

于 2009-06-07T16:13:40.923 回答
1

好的。您很快就会得到分析器的答案。如果你有一台 AMD 机器,我强烈推荐免费的 AMD 的 CodeAnalyst。

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

于 2009-06-07T16:14:52.937 回答
1

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

堆可以使用 glib malloc 延迟分配新对象,但在释放时只需将项目推送到堆上。当您需要分配时,如果有可用的已释放对象,则可以分配该对象。

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

于 2009-06-07T16:23:22.563 回答