申请连续重新分配小内存块的算法是什么?

What algorithm to apply for continuious reallocation of small memory chunks?

在 C 程序中,我遇到需要大量内存块的事务,我需要知道是否有用于处理所有这些的算法或最佳实践技术 malloc/free,我使用数组来存储这些内存块,但在某些时候数组本身变满,重新分配数组只会造成更多浪费,处理这个问题的优雅方法是什么?

这种情况下的最佳算法是free list分配器+二叉搜索树。

你向系统申请一个大内存块,然后从这个内存块中分配固定大小的内存块。当块已满时,您正在分配另一个块,并且 link 它们进入红黑或 AVL 二叉搜索区间树(否则在 free 期间通过遍历块列表搜索块成为瓶颈)

同时,多线程和线程同步变得很重要。如果您只是使用互斥锁或普通的自旋锁,您会发现 libc malloc 的工作速度比您的自定义内存分配器快得多。可以在 Hazard pointer 中找到解决方案,即每个线程都有自己的内存分配器(或每个 CPU 核心一个分配器)。这将增加另一个问题 - 当一个线程分配内存而另一个线程释放它时,这将需要在 free 函数期间搜索确切的分配器,并需要 strick 锁定或无锁数据结构。

最佳选择 - 您可以使用 jemalloc, tcmalloc 或任何其他通用建议的快速内存分配器来完全或部分替换您的 libc(即 pdmalloc)默认分配器。

如果由于要提供的功能而需要跟踪这些不同的缓冲区,那么您将需要某种缓冲区管理功能,其中包括为缓冲区管理分配内存。

但是,如果您担心内存碎片,那就是另一回事了。

我看到很多关于使用 malloc()free() 如何导致内存碎片以及减少或消除此问题的各种方法的文章。

我怀疑几年前,这可能是个问题。然而,这些天我怀疑大多数程序员在管理内存方面是否能像开发 运行 现代编译器时代的人一样出色。我确信有特殊的应用程序,但编译器 运行 开发时非常清楚内存碎片,他们使用许多方法来帮助缓解问题。

例如,这是一篇 2010 年的旧文章,描述了现代编译器 运行time 实现 malloc()free()A look at how malloc works on the Mac

这是关于 GNU allocator 的一些信息。

IBM 2004 年 11 月的这篇文章描述了内存管理的一些注意事项,Inside memory management 并提供了他们所谓的 "code for a simplistic implementation of malloc and free to help demonstrate what is involved with managing memory." 请注意,这是一个简单的示例,旨在说明一些问题,并不是当前实践的示范。

我用 Visual Studio 2015 做了一个快速控制台应用程序,它在 C 源文件中调用了一个 C 函数,该文件穿插了各种大小的 malloc()free() 调用。我在 Windows 任务管理器中观察进程时 运行 这个。峰值工作集(内存)最大为 34MB。观察内存(私有工作集)测量,我看到它随着程序的上升和下降 运行。

#include <malloc.h>
#include <stdio.h>

void funcAlloc(void)
{
    char *p[50000] = { 0 };
    int   i;

    for (i = 0; i < 50000; i+= 2) {
        p[i] = malloc(32 + (i % 1000));
    }

    for (i = 0; i < 50000; i += 2) {
        free(p[i]); p[i] = 0;
    }
    for (i = 1; i < 50000; i += 2) {
        p[i] = malloc(32 + (i % 1000));
    }
    for (i = 1; i < 50000; i += 2) {
        free(p[i]); p[i] = 0;
    }

    for (i = 0; i < 50000; i++) {
        p[i] = malloc(32 + (i % 1000));
    }

    for (i = 0; i < 50000; i += 3) {
        free(p[i]); p[i] = 0;
    }
    for (i = 1; i < 50000; i += 3) {
        free(p[i]); p[i] = 0;
    }
    for (i = 2; i < 50000; i += 3) {
        free(p[i]); p[i] = 0;
    }
}

void funcMain(void)
{
    for (int i = 0; i < 5000; i++) {
        funcAlloc();
    }
}

在使用 malloc()free() 搅动内存的某些条件下,程序员应该练习帮助内存分配器的唯一考虑可能是使用一组标准缓冲区大小来改变长度数据。

例如,如果您要为多个不同大小的数据结构创建临时缓冲区,请对所有缓冲区使用最大 struct 的标准缓冲区大小,以便内存管理器正常工作具有相同大小的内存块,因此它能够更有效地重用空闲内存块。我已经看到一些动态数据结构功能使用这种方法,例如分配一个最小长度为 32 个字符的动态字符串或将缓冲区请求四舍五入为四或八的倍数。