提问人:doliphin 提问时间:12/30/2022 最后编辑:doliphin 更新时间:2/13/2023 访问量:1243
Block、Stack 和 Scratch 分配器之间有什么区别?
What are the differences between Block, Stack and Scratch Allocators?
问:
Mike Acton 在他的演讲“为引擎开发人员解决正确的问题”中说:
绝大多数情况下,您只需要这三种类型的分配器:块分配器、堆栈分配器和临时分配器
但是,他没有详细说明这些类型的分配器之间的区别是什么。
我认为“堆栈分配器”只是一个基于堆栈的分配器,但我听说过的所有其他类型(包括“竞技场”)听起来都像是做同样事情的花哨方式,即“分配一个大块并以一种非常有效的方式将其分块,然后在完成后释放它”
那么,这些分配器之间有什么区别,每个分配器有什么优点,为什么我“绝大多数时间”只需要这三个?
答:
正如评论中所指出的,演讲中使用的术语在行业内尚未得到很好的认可,因此对于这里所指的确切分配策略存在一些疑问。考虑到游戏编程文献中经常提到的内容,以下是我根据的三个分配器背后的猜测:
块分配器
也称为池分配器。这是一个分配器,它只分发大小的内存块,而不管用户实际请求了多少内存。
假设您有一个块大小为 100 字节的块分配器。您想为单个 64 位整数分配内存吗?它为您提供一个 100 字节的块。您想为 20 个单精度浮点数的数组分配内存吗?它为您提供一个 100 字节的块。您想为具有 101 个字符的 ASCII 字符串分配内存吗?它给你一个错误,因为它不能把你的字符串塞进 100 个字节。
区块分配器有几个优点。它们相对容易实现,并且不会受到外部内存碎片的影响。它们通常还表现出非常可预测的运行时行为,这对于视频游戏通常是必不可少的。它们非常适合大多数分配大小大致相同的问题,而显然不太适合这种情况。
除了这里描述的最简单的版本,即每个分配器只支持单个块大小之外,还存在更灵活的扩展,支持多个块大小,而不会过多地损害上述优势。
堆栈分配器
堆栈分配器的工作方式与堆栈类似:您只能按分配的相反顺序进行释放。如果随后分配对象,则 ,如果不放弃,则无法回收内存。A
B
A
B
堆栈分配器非常容易实现,因为您只需要跟踪一个指针,该指针标记已使用和未使用的内存区域之间的分隔。分配将该指针移动到一个方向,而解除分配则将其移动到相反的方向。
堆栈分配器可以最佳地有效地利用内存,并具有完全可预测的运行时行为。显然,它们只适用于易于实现所需释放顺序的问题。静态地执行正确的释放顺序通常并非易事,因此,如果不小心使用它们,调试它们可能会很痛苦。
暂存分配器
也称为单调分配器。临时分配器的工作方式类似于堆栈分配器。分配的工作方式完全相同。解除分配是禁忌。也就是说,一旦分配了内存,就无法回收。
如果要取回内存,则必须销毁整个暂存分配器,从而一次性释放其所有内存。
暂存分配器的优点与堆栈分配器的优点相同。它们非常适合可以自然地识别不再需要所有已分配对象的点的问题。与堆栈分配器类似,如果使用不慎,如果分配器在仍有活动对象处于活动状态时被销毁,则可能会导致令人讨厌的运行时错误。
为什么我只需要这三个?
经验表明,在许多领域中,不需要完全动态的内存管理。分配生存期可以按通用大小(块分配器)或通用生存期(暂存和堆栈分配器)进行分组。如果在这样一个领域工作的工程师愿意经历相应地对每个分配进行分类的麻烦,那么他们可能只需要这三种分配策略就可以满足他们的大部分动态内存需求,而不会引入不合理的额外开发工作。作为对他们努力的奖励,他们将受益于这些算法的良好运行时属性,特别是非常快和可预测的执行时间,以及可预测的内存消耗。
如果您所在的领域更难根据这些术语对分配进行分类;或者如果您不能或不愿意花费额外的工程工作;或者,如果您正在处理一个不能很好地映射到这三个分配器的特殊用例 - 您可能仍然希望使用通用分配器,即 good old malloc。
演讲中提出的观点更多是,如果您确实需要担心自定义内存分配 - 尤其是在具有特定要求和权衡的视频游戏领域 - 这三种类型的分配器可以很好地解决您在天真地单独依赖通用分配器时可能遇到的特定问题。
不久前,我做了一个关于C++分配器的长篇演讲,如果你还想了解更多,我会更详细地解释所有这些。
评论
UnsafeScratchAllocator