Эффективность повторных выделений памяти в glibc - PullRequest
1 голос
/ 07 июня 2009

Ниже моя оболочка C для подпрограммы Fortran ZHEEVR из известной числовой библиотеки LAPACK:

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» (имена параметров соответствуют условию Фортрана для этой функции) для одного и того же размера матрицы. Я думаю, что размеры рабочих массивов в большинстве случаев будут одинаковыми, так как диагональные матрицы будут иметь одинаковую структуру. Мои вопросы:

  1. Могут ли повторяющиеся вызовы alloc / free ("alloc_memory" - простая оболочка вокруг malloc glibc) вызвать снижение производительности и насколько сильно?
  2. Имеет ли значение порядок свободных? Должен ли я освободить последний выделенный массив первым или последним?

Ответы [ 5 ]

5 голосов
/ 07 июня 2009
  • Можете ли вы использовать 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 заключается в том, что вы не можете освободиться.

(непроверенный код!)

5 голосов
/ 07 июня 2009

1) Да, могут.

2) Любой здравомыслящий libc не должен беспокоиться о порядке free (). С точки зрения производительности это тоже не имеет значения.

Я бы порекомендовал убрать управление памятью из этой функции - чтобы вызывающая сторона предоставляла размер матрицы и выделяла временные буферы. Это значительно сократит количество malloc, если эта функция вызывается из одного и того же места на матрице одинакового размера.

2 голосов
/ 07 июня 2009

Это, безусловно, повлияет на производительность - сколько вы можете узнать наверняка только по времени. Чтобы создать версию, которая избегает большинства выделений, выделите статический указатель и запомните размер в другом статическом целом числе. Если следующий вызов использует тот же размер, просто используйте то, что было выделено в прошлый раз. Освободите все что угодно, когда вам нужно создать новую матрицу, потому что размер изменился.

Обратите внимание, что это решение подходит только для однопоточного кода.

1 голос
/ 07 июня 2009

Если вы выделяете один и тот же элемент размера сотни тысяч раз, то почему бы просто не поддерживать кучу ваших объектов (поскольку они кажутся относительно простыми, то есть не содержат указателей на другую выделенную память) и свободны на Ваша собственная куча (или стек на самом деле)?

Куча может лениво распределять новые объекты с помощью glib malloc, но при освобождении просто помещать элемент в кучу. Когда вам нужно выделить, если есть доступный освобожденный объект, он может просто выделить его.

Это также сэкономит вам несколько вызовов на распределение (поскольку вам не нужно делать какое-либо распределение, и похоже, что ваша подпрограмма делает несколько вызовов malloc), а также позволит избежать фрагментации (до некоторой степени) по крайней мере на используемая память. Конечно, начальные распределения (и другие распределения, когда программа работает, когда ей нужно расширить эту память), могут вызвать фрагментацию, но если вы действительно беспокоитесь об этом, вы можете запустить некоторую статистику и найти средний / максимальный / типичный размер вашего куча во время выполнения и предварительно выделить ее сразу при запуске программы, избегая фрагментации.

1 голос
/ 07 июня 2009

Хорошо. Вы получите ответ профайлера в ближайшее время. Если у вас есть машина AMD, я настоятельно рекомендую бесплатную программу AMD CodeAnalyst.

Что касается проблемы с памятью, я думаю, что в этом случае вы могли бы работать с локальным управлением памятью. Просто определите максимальное количество памяти, которое вы можете выделить для этой функции. Затем вы объявляете статический буфер и работаете с ним, как с компилятором в стеке. Я однажды сделал обертку над VirtualAlloc, и она ОЧЕНЬ быстра.

...