【问题标题】:Linked lists. Where to allocate and how to cope with fragmentation?链接列表。在哪里分配以及如何应对碎片?
【发布时间】:2013-04-30 05:56:16
【问题描述】:
  • 位置
    • 在堆中,碎片化(每个节点都使用 malloc) - 在几种不同的方面效率低下(缓慢分配、缓慢访问、内存碎片)
    • 在堆中,在一大块中 - 当需要重新分配时,数据结构获得的所有灵活性都会丢失
    • 在堆栈中 - 堆栈的大小往往相当有限,因此根本不建议在其上分配大型结构

它们的最大优势,插入 O(1),在碎片化内存和数千次调用内存分配器给我们另外 10 个字节的环境中似乎相当无用。


编辑澄清:
这个问题是在一次采访中被问到的。这不是一个职场问题,因此希望从一小组标准算法中盲目地做出正确决定的通常启发式方法并不适用。

现有的答案和 cmets 提到“malloc 并没有那么慢”,“malloc 部分对抗碎片”。好的,如果我们使用另一种数据结构,例如 C++ 向量的 C 端口(即 - 分配足够大小的顺序内存,如果数据扩展,则重新分配到两倍大的块)所有问题都解决了,但我们失去了快速插入/删除。链表(分配在哪里?)比向量有很大优势的任何场景?

【问题讨论】:

  • 大多数 malloc 实现已经有代码来减少碎片的影响(例如,它们已经分配了更大的块并给了你一块)。
  • 为什么说插入 O(1) 失去了优势?即使在碎片内存上,内存分配操作也不会那么昂贵。
  • @Geek,Alligment,碎片,泄漏,指针间接 - 在独立内存块中静态分配的列表有很多缺点。只是为了快速插入/删除。
  • @Vorac:您是否考虑过操作系统分页可能对缓存局部性产生的影响,如果它在向/从二级存储分页时执行合理的重定位?
  • 这整个问题太天真了...使用大块内存只是意味着您正在实现自己的堆例程 - 将问题转移到经验不足的手中:只有当你有一些时才有可能获胜特别了解节点的确切大小或相对寿命。这里有意义的一般问题是向量和链表之间的旧性能权衡 - 当然链表有时具有巨大的优势......例如一个最近最少使用的容器(用于缓存),其中刚刚使用的节点需要不断地移到前面。列表节点有稳定的地址:另一大优势。

标签: c memory-management


【解决方案1】:

这听起来像是过早的优化。我认为正确的做法是:

  1. 尽可能使用最简单的实现;
  2. 分析整个程序。
  3. 如果分析显示列表存在性能问题,请考虑替代实现(包括替代分配器)。

【讨论】:

    【解决方案2】:

    如果您担心标准分配器无法有效处理您的专用 10 字节分配,请编写一个自定义分配器,该分配器从标准 (malloc()) 分配器中获取大量内存并有效地分配小项目。当您在初始大块中耗尽内存时,您应该重新分配;你应该分配一个新的(额外的)大块并从中分配。您可以决定如何处理释放的内存。您可以简单地忽略发布,直到您在列表处理结束时释放所有大块。或者,您可以通过跟踪大块中释放的内存来使生活复杂化。这总体上减少了内存使用量,但初始编写更复杂。

    另一方面,您应该注意过早优化的风险。您是否测量过性能影响?鉴于我对您最近的问题的了解,您可能应该坚持使用malloc(),而不是尝试编写自己的分配器(目前)。

    【讨论】:

      【解决方案3】:

      理想情况下,您的链表实现不应使用上述任何一种。应该由调用者分配和销毁内存。想想sprintffgets 等函数……它们是否分配了任何内存?不,这是有原因的:简单。想象一下,如果你必须 freefgets 获得的所有东西(或者更糟糕的是,fscanf)。那不会很痛苦吗?尝试开发您的函数以与标准库保持一致。

      声明listnode_alloc 函数可能会有所帮助,但这只会包装malloclistnode_init 函数。考虑realloc 如何处理NULL 输入...

      【讨论】:

      • 绝对!但是,您会向调用者推荐什么示例内存分配方案,以促进插入链表的增长?
      • 这取决于程序正在解决的任何有用问题。也许可能需要开发一组池处理函数,但它们应该独立于链表实现,因为这种池处理也适用于使用其他数据结构的情况。此外,这种优化应该基于指示分配实际上显着阻碍性能的启发式方法(例如分析)。通过开发您的链表实现,使其不关心分配/解除分配,您可以使优化在以后易于实现
      • @Vorac 简而言之,用抽象来解决问题,以便在以后轻松实现优化(这恰好与标准库设计一致)。解决问题后,如果您需要优化程序,请根据分析器所说的进行。
      • 您的 cmets 和 Tony D 给了我一些关于这个主题的见解。我想剩下的会随着经验而来。谢谢。
      • @Vorac Here's 一个我不久前写的例子,用于另一个问题。考虑允许qsort 对任何数组进行排序以及bsearch 搜索任何(排序的)数组的抽象,而不管其类型如何。你将如何实现list_find_value?您似乎已经从对象的角度考虑过缓存局部性......这种抽象如何改善代码的缓存局部性?
      【解决方案4】:

      这可能不是确切的解决方案,而是另一种方法

      • 自行处理碎片。
        • 分配大内存池
        • 为每个新节点提供来自该节点的内存,直到有空闲内存为止。
        • 如果使用了池,请分配另一个池并使用它来分配新块

      然而,这说起来容易做起来难。这样做可能会遇到很多问题。

      所以,建议让这种优化到malloc及相关功能。

      从堆栈分配不是一个可行的选择。你不能从堆栈中malloc。您将不得不在某些函数中预先分配大块缓冲区。在这种情况下,数组比链表好。

      【讨论】:

        【解决方案5】:

        嗯,内存分配策略可能会因内存碎片、数千个系统调用等而有所不同,这正是使用 O 的原因! ;-)

        【讨论】:

          猜你喜欢
          • 2012-05-21
          • 2012-03-07
          • 2011-09-04
          • 1970-01-01
          • 2023-04-02
          • 1970-01-01
          • 1970-01-01
          • 2013-01-29
          • 2010-10-18
          相关资源
          最近更新 更多