使用荒谬的内存量来实现堆栈 - C.

时间:2012-04-11 15:43:22

标签: c memory-management data-structures

我在C中编写min-heap的实现,作为Dijkstra算法的一部分。我已经完成了所有细节,我的测试程序通过了valgrind测试,但它在这个过程中分配了大量的内存。最终测试是在INT_MAXINT_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);
    }
}

非常感谢任何帮助。

1 个答案:

答案 0 :(得分:2)

这是我的工作理论。我愿意发现我错了,但没有剩下的代码,我就无法检测,运行和测试它。

... struct heap { ... Elt *elts; } ...保存复制4个int的成本并用复制1指针替换它时typedef struct elt {...} *Elt;的间接,但复制速度很快,而且只发生log2(N)次。

相反,每个struct elt都是单独的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的数量
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和内存的内存量。

基本上,应该只有(大约)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);
    }
}

到目前为止,在每个元素 元素>上调用floatdown。因此heap_insert的运行时间约为(N ^ 2)/ 2 (即O(N ^ 2))运行时间。

我相信heap_insert应该为它添加到堆中的每个元素使用floatDown,而不是heapify