Почему вещи, которые используются внутри openmp параллельных блоков, не собираются Boehm GC впоследствии? - PullRequest
0 голосов
/ 10 мая 2019

Я использую Boehm-GC в моей C-программе для сбора мусора. Я пытаюсь распараллелить цикл for, который работает с массивом. Массив выделяется через GC_malloc. Когда цикл завершен, массив больше не используется в программе. Я вызываю GC_gcollect_and_unmap, который освобождает массив. Однако когда я распараллеливаю цикл for с использованием openmp, массив никогда не освобождается после завершения цикла. Это точно такая же программа, я только добавляю #pragmas вокруг цикла, чтобы распараллелить его. Я попытался посмотреть на ассемблерный код бок о бок с параллелизацией openmp и без нее, я вижу, что указатель массива обрабатывается аналогичным образом, и не вижу где-либо дополнительных указателей. Единственное отличие состоит в том, что цикл for реализован в виде простого цикла в основной функции, но когда я распараллеливаю его, openmp создает новую функцию ## name ## ._ omp_fn и вызывает ее. В любом случае, мне нужно что-то сделать, чтобы Boehm-GC собирал массив? Мне трудно опубликовать MWE, потому что, если программа достаточно мала, Boehm-GC вообще не запускается.

Вот фрагмент кода без распараллеливания.

  struct thing {
    float* arr;
    int size;
  }
  int l=10;
  static thing* get_randn(void) {
    thing* object = (thing*)GC_malloc(sizeof(struct {float* arr, int size}));
    object->arr=malloc(sizeof(float)*l);
    void finalizer(void *obj, void* client_data)
    { 
      printf("freeing %p\n", obj); 
      thing* object = (thing*)obj;
      free(object->arr);
    }
    GC_register_finalizer(object, &finalizer, NULL, NULL, NULL);
    float *arr = object->arr; 
    int t_id;
    for (t_id = 0; t_id<l; t_id++) { 
       torch_randn(arr+t_id); 
    } 
    return object;                          
  }                                 

Приведенный выше мусор кода собирает объект, созданный функцией. Ниже приведен код с распараллеливанием.

  struct thing {
    float* arr;
    int size;
  }
  int l=10;
  static thing* get_randn(void) {
    thing* object = (thing*)GC_malloc(sizeof(struct {float* arr, int size}));
    object->arr=malloc(sizeof(float)*l);
    void finalizer(void *obj, void* client_data)
    { 
      printf("freeing %p\n", obj); 
      thing* object = (thing*)obj;
      free(object->arr);
    }
    GC_register_finalizer(object, &finalizer, NULL, NULL, NULL);
    float *arr = object->arr; 
    int t_id;
    #pragma omp parallel num_threads(10)
    {
     #pragma omp for
     for (t_id = 0; t_id<l; t_id++) { 
       torch_randn(arr+t_id); 
     }
    } 
    return object;                          
  }                                 

Для этого кода объект не получает мусор. Трудно воспроизвести проблему самостоятельно с помощью MWE, потому что сборщик мусора не запускается для небольших программ, но я наблюдаю такое поведение, когда я запускаю свою полную программу.

1 Ответ

0 голосов
/ 17 мая 2019

Трудно воспроизвести проблему самостоятельно с помощью MWE, потому что сборщик мусора не запускается для небольших программ, но я наблюдаю такое поведение при запуске с полной программой.

Вы можете форсировать сборку мусора, вызывая GC_gcollect().

Также Boehm-GC определенно освобождает память / объекты, расположенные в параллельных секциях.Но есть по крайней мере одно предостережение: OpenMP использует внутренний пул потоков.Это означает, что потоки не обязательно заканчиваются после окончания параллельной секции.Эти объединенные в пул и свободные потоки могут все еще иметь ссылки на объекты в куче.

Рассмотрим следующую программу, которая запускает четыре потока параллельно и выделяет тысячу «объектов» на поток:

#define GC_THREADS
#include <assert.h>
#include <stdio.h>
#include <omp.h>
#include <gc.h>

#define N_THREADS 4
#define N 1000

// count of finalized objects per thread
static int counters[N_THREADS];

void finalizer(void *obj, void* client_data)
{
#pragma omp atomic
    counters[*(int*)obj]++;
}

int main(void)
{
    GC_INIT();
    GC_allow_register_threads();

    int i;
    for(i = 0; i < N_THREADS; i++) {
        counters[i] = 0;
    }

    // allocate lots integers and store the thread id in it
    // execute N iterations per thread
#pragma omp parallel for num_threads(4) schedule(static, N)
    for (i = 0; i < N_THREADS*N; i++)
    {
        struct GC_stack_base sb;
        GC_get_stack_base(&sb);
        GC_register_my_thread(&sb);

        int *p;
        p = (int*)GC_MALLOC(4);
        GC_REGISTER_FINALIZER(p, &finalizer, NULL, NULL, NULL);
        *p = omp_get_thread_num();
    }

    GC_gcollect();
    for(i = 0; i < N_THREADS; i++) {
        printf("finalized objects in thread %d: %d of %d\n", i, counters[i], N);
    }
    return 0;
}

Пример вывода:

finalized objects in thread 0: 1000 of 1000
finalized objects in thread 1: 999 of 1000
finalized objects in thread 2: 999 of 1000
finalized objects in thread 3: 999 of 1000

Числа означают, что потоки с 1 по 3 объединены и все еще содержат ссылку на объект последней итерации.Поток 0 является основным потоком, который продолжает выполнение и, таким образом, теряет ссылку на последнюю итерацию в стеке.

Редактировать: @maddy: Я не думаю, что это имеет какое-либо отношение крегистры или оптимизация компилятора.Как правило, компилятор может выполнять только те оптимизации, которые гарантированно не изменят поведение программы.По общему признанию, ваша проблема может быть угловой.

Согласно Wikipedia , Boehm-GC ищет ссылки в программном стеке.В зависимости от того, как компилятор преобразует прагмы openmp в код, вполне может оказаться, что стековый фрейм, содержащий ссылку на кучу, все еще действителен, когда поток переходит в состояние ожидания.В этом случае Boehm-GC по определению не может завершить указанный объект / память.Но об этом ИМХО сложно рассуждать.Вам нужно было бы хорошо понять, что ваш компилятор делает с прагмами openmp и как именно Boehm-GC анализирует стек программ.

Суть в том, как только вы повторно используете потоки (запуская что-то еще сopenmp) стеки объединенных потоков будут перезаписаны, и Boehm-GC сможет восстановить память из предыдущей параллельной итерации. В долгосрочной перспективе у вас нет утечки памяти.

...