我在我的C程序中使用Boehm-GC进行垃圾收集。我正在尝试并行化一个在数组上工作的for循环。该阵列是通过GC_malloc分配的。当循环执行完毕后,该数组在程序中不再使用。我调用GC_gcollect_and_unmap来释放数组。然而,当我使用openmp并行化for循环时,在执行完循环之后,数组永远不会被释放。这是完全相同的程序,我只是在循环周围添加了#杂注来并行化它。我试过在使用和不使用openmp并行化的情况下并排查看汇编代码,我发现数组指针正在以类似的方式进行处理,并且没有看到额外的指针被保存在任何地方。惟一的区别是,for循环是作为main函数中的一个简单循环实现的,但当我将其并行化时,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本身很难重现问题,因为垃圾收集器不会在小程序中发挥作用,但我在运行完整程序时观察到了这种行为。
发布于 2019-05-17 22:08:27
通过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不能完成被引用的对象/内存。但是很难对这个IMHO进行推理。您需要很好地理解您的编译器对openmp编译指示做了什么,以及Boehm-GC如何准确地分析程序堆栈。
关键是:一旦您重用线程(通过使用openmp运行其他程序),池化线程的堆栈将被覆盖,Boehm-GC将能够从上一次并行迭代中回收内存。从长远来看,您不会泄漏内存。
https://stackoverflow.com/questions/56084397
复制相似问题