使用大量内存的堆实现-C

使用大量内存的堆实现-C,c,memory-management,data-structures,C,Memory Management,Data Structures,作为Dijkstra算法的一部分,我正在用C编写一个minheap的实现。我已经记下了所有细节,我的测试程序通过了valgrind测试,但它在这个过程中分配了大量的内存。最后的测试是在一个由INT\u MAX组成的网格上进行的(坐标仅为整数),测试时我得到SIGXCPU错误。即使我只是在队列中插入16k位置,然后删除所有内容,这仍然需要很长时间,并且分配的空间超过8MB。当我在巨大的网格测试用例上运行它时,在我手动退出之前,它可以达到500MB。会发生什么事?以下是我的部分代码: struct

作为Dijkstra算法的一部分,我正在用C编写一个
minheap
的实现。我已经记下了所有细节,我的测试程序通过了valgrind测试,但它在这个过程中分配了大量的内存。最后的测试是在一个由
INT\u MAX
组成的网格上进行的(坐标仅为整数),测试时我得到
SIGXCPU
错误。即使我只是在队列中插入
16k
位置,然后删除所有内容,这仍然需要很长时间,并且分配的空间超过
8MB
。当我在巨大的网格测试用例上运行它时,在我手动退出之前,它可以达到
500MB
。会发生什么事?以下是我的部分代码:

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;
}

任何帮助都将不胜感激。

这是我的工作原理。我愿意发现我错了,但是如果没有其余的代码,我就无法检测、运行和测试它

的间接性。。。结构堆{…Elt*elts;}…
when
typedef struct Elt{…}*Elt
节省了复制4个int并用复制1个指针替换它的成本,但是复制速度很快,而且只发生log2(N)次

相反,每个
结构elt
都是单独的malloc'd。不必四处寻找malloc'd块的实际大小,我们可以估计,平均而言,这将浪费N/2 sizeof(struct elt)(实际上,我认为在我的机器上更糟糕)

它还可能创建不连续的内存块(通过在较大的块之间放置小块),因此realloc必须始终分配较大的块,因此重用以前的块会更加困难。在这个特定的例子中,我不认为这和内部碎片造成的浪费或malloc的大量调用一样重要

它还可能创建一个“缓存清除器”。实际值分布在整个内存中,由于malloc的struct elt块的内部碎片,缓存线相对稀疏

因此,请替换:

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;
}

这将进一步减少malloc'ed和free'd的内存量

本质上,应该只有(大约)realloc的log2(N)调用。realloc也有可能只是扩展现有的块而不是副本


编辑:

与内存分配相比,
heap\u 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);
    }
}
对于插入的每个元素,它调用堆中到目前为止的每个元素
floatdown
。因此,
heap\u insert
的运行时大约为(N^2)/2(即O(N^2))运行时


我认为
heap\u insert
应该为添加到堆中的每个元素使用
浮动
,而不是
heapify

我觉得
elt\u assign
方法在这里也会派上用场。我同意Makoto的观点,因为你发布的代码没有明显的泄漏。你说“它在这个过程中分配了荒谬的内存量”。多少钱?打印malloc和realloc返回的值可能有一些启示。(我将使用包装函数myMalloc和myRealloc)。可能是在浮动开始时,内存被Elt
x=malloc…
分割,所以我想知道heap\u insert中的每个realloc是否以某种低效的方式分配了一段全新的内存。此外,每一个malloc和realloc的时间,并打印时间,可能显示指数放缓。将
Elt x=malloc
替换为
typedef struct Elt e=a[pos]我省略了elt_assign函数,因为在这个问题上我已经有很多代码了,但是它只是e->p.x=temp->p.x等等。谢谢gbulmer,我将尝试这两种方法。我很有信心,我已经从10天运行2^x插入的速度呈指数级下降≤x≤1616岁时,速度慢得让人无法忍受。我并不是说每个alloc分配的内存都超出了合理范围,但如果我在堆中插入8000个elt,valgrind将报告我在50万次分配中分配了8 MB(全部释放,但仍然太多)。还有,在你上次的建议中,为什么要有typedef?@jclancy-对不起,我复制错了+粘贴错了<代码>类型定义结构elt e=a[pos]应该是
struct elt e=a[pos]。将浮动中的
Elt x=malloc…
更改为
struct Elt e=a[pos]可能会显著减少malloc'ed和free'd的空间量;如果floatdown被调用(N.log N)次,那就是相当多的malloc+自由调用。@wildplasser-我不喜欢typedef在代码需要处理指针时隐藏指针。我不喜欢像
Elt x=malloc(sizeof(struct Elt))。我更喜欢指针是显式的<代码>Elt*x=malloc(sizeof(Elt))立即就有意义(对我来说)。类似地,我将更改
typedef结构堆{…}*堆
typedef结构堆{…}堆这样像
void heap\u insert(heap h,…){if(h->capacity<(h->size+2)){h->elts=…
变成
void heap\u insert(heap*h,…){if(h->capacity<(h->size+2)){h->elts=…
,我发现这一点更明显。好的,谢谢,我做了修改,明天将进行测试。你能这样做结构赋值吗,只用一个等号,而不是单独做所有事情吗?@jclancy-除非你使用的是非常旧的(1990年以前)C编译器,是的,结构赋值是有效的。即使您不喜欢我建议的更改,我认为重要的部分是删除大量的malloc调用;我认为它们导致了您描述的许多问题。因此,请尝试更改。我认为这将产生很大的影响。如果没有改进,请报告您的发现。
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;
}
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;
}
void heap_insert(Heap h, Elt e, int *counter) {
    ...
    heapify(h->size, h->elts);
    ...
}
static void heapify(int n, Elt *a) {
    for(int i = n - 1; i >= 0; i--) {
        floatDown(n, a, i);
    }
}