【问题标题】:Is there any benefit to use multiple heaps for memory management purposes?使用多个堆进行内存管理有什么好处吗?
【发布时间】:2013-11-27 21:19:08
【问题描述】:

我是系统软件学院的学生。现在我正在为 Windows 开发一个内存管理器。这是我对malloc()free() 的简单实现:

HANDLE heap = HeapCreate(0, 0, 0);

void* hmalloc(size_t size)
{
    return HeapAlloc(heap, 0, size);
}

void hfree(void* memory)
{
    HeapFree(heap, 0, memory);
}

int main()
{
    int* ptr1 = (int*)hmalloc(100*sizeof(int));
    int* ptr2 = (int*)hmalloc(100*sizeof(int));
    int* ptr3 = (int*)hmalloc(100*sizeof(int));

    hfree(ptr2);
    hfree(ptr3);
    hfree(ptr1);

    return 0;
}

它工作正常。但我不明白是否有理由使用多个堆?好吧,我可以在堆中分配内存并获得分配内存块的地址。但这里我使用 ONE 堆。有理由使用多个堆吗?也许对于多线程/多进程应用程序?请解释一下。

【问题讨论】:

  • 只是好奇,如果是你的实现,你不应该知道你为什么要创建 3 吗?
  • 我刚刚测试了一些东西。会不会引起一些问题?
  • 是的,看我的回答。您使用的是 C++ 而不是 C,您必须确保调用了析构函数。

标签: c++ winapi memory-management


【解决方案1】:

使用多个堆/自定义分配器的主要原因是为了更好地控制内存。通常在大量新/删除之后,内存会变得碎片化并且应用程序的性能下降(应用程序也会消耗更多内存)。在更可控的环境中使用内存可以减少堆碎片。

另外一种用法是防止应用程序中的内存泄漏,您可以只释放您分配的整个堆,而无需费心释放那里分配的所有对象。

另一种用法是紧密分配的对象,例如,如果您有一个列表,那么您可以将所有节点分配到一个较小的专用堆中,并且应用程序将获得性能,因为在迭代节点时缓存未命中率会减少。

编辑:然而,内存管理是一个很难的话题,在某些情况下它做得不对。 Andrei Alexandrescu 曾发表过一次演讲,他说对于某些应用程序,将自定义分配器替换为默认分配器可以提高应用程序的性能。

【讨论】:

  • malloc/free 通常会自动创建/释放堆以减少碎片吗?
  • 你不能只是“删除堆”——你可以释放它,但你不应该这样做。 delete 使用析构函数(除了 free 之外,这对于 void* 来说什么都不是)如果你删除了一大片你不会破坏其中的东西,如果你有容器结构等,它们可能有指向其他堆的指针。他确实说 C++。
  • '防止内存泄漏'——这是有问题的
  • @Alec Teal 我更改了“删除”,它在那里不合适。 @Dieter Lücking 是的,它经常在游戏中完成,而不仅仅是小游戏,tripla A 标题使用这样的技术。
  • 您仍然不应该删除它,我认为您将分配器与区域混淆了。
【解决方案2】:

这是一个很好的链接,详细说明了为什么您可能需要多个堆: https://caligari.dartmouth.edu/doc/ibmcxx/en_US/doc/libref/concepts/cumemmng.htm

"Why Use Multiple Heaps?
Using a single runtime heap is fine for most programs. However, using multiple 
heaps can be more efficient and can help you improve your program's performance 
and reduce wasted memory for a number of reasons:

1- When you allocate from a single heap, you may end up with memory blocks on
   different pages of memory. For example, you might have a linked list that 
   allocates memory each time you add a node to the list. If you allocate memory for
   other data in between adding nodes, the memory blocks for the nodes could end up
   on many different pages. To access the data in the list, the system may have to 
   swap many pages, which can significantly slow your program.

   With multiple heaps, you can specify which heap you allocate from. For example, 
   you might create a heap specifically for the linked list. The list's memory blocks 
   and the data they contain would remain close together on fewer pages, reducing the 
   amount of swapping required.

 2- In multithread applications, only one thread can access the heap at a time to 
    ensure memory is safely allocated and freed. For example, say thread 1 is 
    allocating memory, and thread 2 has a call to free. Thread 2 must wait until 
    thread 1 has finished its allocation before it can access the heap. Again, this 
    can slow down performance, especially if your program does a lot of memory 
    operations.

    If you create a separate heap for each thread, you can allocate from them 
    concurrently, eliminating both the waiting period and the overhead required to 
    serialize access to the heap.


 3- With a single heap, you must explicitly free each block that you allocate. If you 
    have a linked list that allocates memory for each node, you have to traverse the 
    entire list and free each block individually, which can take some time.

    If you create a separate heap for that linked list, you can destroy it with a 
    single call and free all the memory at once.

  4- When you have only one heap, all components share it (including the IBM C and 
     C++ Compilers runtime library, vendor libraries, and your own code). If one 
     component corrupts the heap, another component might fail. You may have trouble 
     discovering the cause of the problem and where the heap was damaged.

     With multiple heaps, you can create a separate heap for each component, so if 
     one damages the heap (for example, by using a freed pointer), the others can 
     continue unaffected. You also know where to look to correct the problem."

【讨论】:

    【解决方案3】:

    一个原因是您需要在内部执行程序的情况,例如运行仿真代码。通过创建自己的堆,您可以允许该堆具有执行权限,出于安全原因,默认情况下该权限已关闭。 (视窗)

    【讨论】:

      【解决方案4】:

      你有一些好的想法,这适用于 C,但在 C++ 中你有析构函数,它们运行非常重要。

      您可以将所有类型视为具有构造函数/析构函数,只是逻辑上“什么都不做”。

      这是关于分配器的。请参阅“伙伴算法”,它使用 2 的幂来对齐和重用内容。

      如果我在某处分配 4 个字节,我的分配器可能会为 4 个字节分配分配一个 4kb 部分。这样我可以在块中放入 1024 个 4 字节的东西,如果我需要更多添加另一个块等等。

      向它请求 4kb 并且它不会在 4byte 块中分配它,它可能有一个单独的用于更大的请求。

      这意味着您可以将大事放在一起。如果我去 17 字节然后 13 字节 1 字节和 13 字节被释放,我只能在其中粘贴

      因此,伙伴系统和 2 的幂,使用 lshift 很容易做到,如果我想要一个 2.5kb 的块,我将它分配为 2 的最小幂(在本例中为 4kb),这样我可以使用

      这不是为了垃圾收集,这只是为了让事情更紧凑和整洁,使用你自己的分配器可以停止对操作系统的调用(取决于 new 和 delete 的默认实现,它们可能已经为你的编译器做了这个)和快速新建/删除。

      堆压缩是非常不同的,你需要一个指向你堆的每个指针的列表,或者某种遍历整个内存图的方法(如 spits Java)所以当你移动东西时并“压缩”它,您可以将指向该事物的所有内容更新到它当前所在的位置。

      【讨论】:

      • 这对我帮助很大。我理解了你的建议并向我的老师解释。他同意了。
      【解决方案5】:

      我唯一一次使用多个堆是在我编写了一个可以构建复杂数据结构的程序时。通过遍历数据结构并释放各个节点来释放数据结构并非易事,但幸运的是,程序只需要临时的数据结构(当它执行特定操作时),所以我使用了一个单独的堆数据结构,这样当我不再需要它时,我可以通过调用 HeapDestroy 来释放它。

      【讨论】:

        猜你喜欢
        • 2021-02-23
        • 1970-01-01
        • 2011-04-24
        • 2016-12-27
        • 2017-08-21
        • 2014-10-26
        • 1970-01-01
        • 2020-02-02
        • 1970-01-01
        相关资源
        最近更新 更多