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

我在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.

点赞