我在C中编写了一个min-heap的实现,作为Dijkstra算法的一部分.我已经完成了所有细节,我的测试程序通过了valgrind测试,但它在这个过程中分配了大量的内存.最后的测试是在INT_MAX的网格上由INT_MAX(坐标只是整数),我测试时得到SIGXCPU错误.即使我只是将16k位置插入队列然后删除所有内容,它仍然需要很长时间并分配超过8 MB.当我在巨大的网格测试用例上运行它时,在我手动退出之前它可以达到500 MB.会发生什么事?这是我的代码的一部分:
struct position {
int x;
int y
};
typedef struct elt {
int priority;
int distance;
struct position p;
} *Elt;
typedef struct heap {
int size;
int capacity;
Elt *elts;
} *Heap;
void heap_insert(Heap h, Elt e, int *counter) {
if(h->capacity < (h->size + 2)) {
h->elts = realloc(h->elts, h->capacity * sizeof(Elt) * 2);
h->capacity *= 2;
}
h->elts[h->size] = malloc(sizeof(*Elt));
elt_assign(h->elts[h->size], e);
h->size++;
heapify(h->size, h->elts);
*counter = *counter + 1;
}
我的所有其他功能都是一次性,功能性或根本不进行内存管理.在这种情况下,初始大小为64,但是从1024开始我的效果相同.我也尝试限制队列的大小,但无济于事.我很确定这不是我的堆码代码,但这只是为了以防万一
static void floatDown(int n, Elt *a, int pos) {
Elt x = malloc(sizeof(struct elt));
elt_assign(x, a[pos]);
for(;;) {
if(Child(pos, 1) < n && a[Child(pos, 1)]->priority < a[Child(pos, 0)]->priority) {
if(a[Child(pos, 1)]->priority < x->priority) {
elt_assign(a[pos], a[Child(pos, 1)]);
pos = Child(pos, 1);
} else {
break;
}
} else if(Child(pos, 0) < n && a[Child(pos, 0)]->priority < x->priority) {
elt_assign(a[pos], a[Child(pos, 0)]);
pos = Child(pos, 0);
} else {
break;
}
}
elt_assign(a[pos], x);
free(x);
}
static void heapify(int n, Elt *a) {
for(int i = n - 1; i >= 0; i--) {
floatDown(n, a, i);
}
}
任何帮助将非常感激.
最佳答案 这是我的工作理论.我愿意发现我错了,但没有剩下的代码,我无法检测,运行和测试它.
… struct heap {… Elt * elts的间接; } …当typedef struct elt {…} * Elt;正在节省复制4个整数的成本并用复制1个指针替换它,但复制速度很快,而且只发生log2(N)次.
相反,每个结构都是单独的malloc.如果没有挖掘来找到malloc’d块的实际大小,我们可以估计平均会浪费N / 2 sizeof(struct elt)(实际上,我认为它在我的机器上更糟).
它还可能创建不连续的内存块(通过在较大的块之间放置小块),因此realloc必须始终分配更大的块,因此重用以前的块将更加困难.在这种特殊情况下,我认为这与内部碎片或malloc的大量调用造成的浪费无关.
它也可能会创建一个“缓存破坏者”.实际值正在整个内存中传播,并且由于malloc’d struct elt块的内部碎片,缓存行相对稀疏.
所以替换:
typedef struct elt {
int priority;
int distance;
struct position p;
} *Elt;
typedef struct heap {
int size;
int capacity;
Elt *elts;
} *Heap;
同
typedef struct elt {
int priority;
int distance;
struct position p;
} Elt; // no longer a pointer
typedef struct heap {
int size;
int capacity;
Elt *elts;
} *Heap;
并改变:
void heap_insert(Heap h, Elt e, int *counter) {
if(h->capacity < (h->size + 2)) {
h->elts = realloc(h->elts, h->capacity * sizeof(Elt) * 2);
h->capacity *= 2;
}
h->elts[h->size] = malloc(sizeof(*Elt));
elt_assign(h->elts[h->size], e);
h->size++;
heapify(h->size, h->elts);
*counter = *counter + 1;
}
至
void heap_insert(Heap h, Elt e, int *counter) {
if(h->capacity < (h->size + 2)) {
h->elts = realloc(h->elts, h->capacity * sizeof(Elt) * 2);
h->capacity *= 2;
}
h->elts[h->size] = e; // no longer need to malloc
h->size++;
heapify(h->size, h->elts);
*counter = *counter + 1;
}
因此,用于保存堆的malloc’d / realloc的内存量应大约为2 * N * sizeof(struct elt).函数/宏elt_assign可能会更改为隐藏其他更改.
然后通过改变来进一步减少malloc’ing的数量:
static void floatDown(int n, Elt *a, int pos) {
Elt x = malloc(sizeof(struct elt));
elt_assign(x, a[pos]);
...
elt_assign(a[pos], x);
free(x);
}
至
static void floatDown(int n, Elt *a, int pos) {
Elt x = a[pos];
...
a[pos] = x;
}
这应该进一步减少malloc’ed和free’d的内存量.
本质上,应该只有(大约)log2(N)调用realloc. realloc可能还有更好的机会扩展现有块而不是副本.
编辑:
heap_insert中存在比内存分配更大的问题:
void heap_insert(Heap h, Elt e, int *counter) {
...
heapify(h->size, h->elts);
...
}
对插入堆中的每个元素调用heapify,即heapify被调用N次. heapify是:
static void heapify(int n, Elt *a) {
for(int i = n - 1; i >= 0; i--) {
floatDown(n, a, i);
}
}
对于插入的每个元素,到目前为止,它会调用堆中的每个元素.因此heap_insert具有大约(N ^ 2)/ 2(即O(N ^ 2))运行时间的运行时间.
我相信heap_insert应该为它添加到堆中的每个元素使用floatDown,而不是heapify.