自定义堆分配器


9

大多数程序在堆分配方面可能很随意,甚至在某种程度上,功能性编程语言更喜欢分配新对象而不是修改旧对象,并使垃圾回收者担心释放对象。

但是,在嵌入式编程中,静默扇区中,由于内存和实时限制,在许多应用程序中根本无法使用堆分配。每种类型将要处理的对象数量是规范的一部分,并且所有内容都是静态分配的。

游戏编程(至少对于那些雄心勃勃地致力于推动硬件发展的游戏)有时介于两者之间:您可以使用动态分配,但有足够的内存和软实时约束,因此您不能将分配器视为黑匣子,更不用说使用垃圾回收了,所以您必须使用自定义分配器。这是C ++仍在游戏行业中广泛使用的原因之一。它可以让您执行以下操作:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2271.html

在这两个领土之间还有哪些其他域?除游戏外,在哪里大量使用自定义分配器?


1
某些OS使用平板分配器,该分配器提供对象缓存,但也可以通过将对象的成员映射到模2 ** N索引缓存的不同集合来减少处理器缓存冲突遗漏(两者都通过在连续内存中具有多个实例以及通过板内的可变填充)。在某些情况下,缓存行为可能比分配/可用速度或内存使用更为重要。
保罗·克莱顿

Answers:


4

每当您的应用程序具有性能密集型的关键路径时,就应该担心如何处理内存。大多数最终用户客户端应用程序不属于此类,因为它们是主要事件驱动的,并且大多数事件来自与用户的交互,并且没有那么多(如果有的话)性能约束。

但是,许多后端软件应将重点放在如何处理内存上,因为许多该软件可以扩展以处理更多数量的客户端,更大数量的事务,更多数据源...。突破极限,您可以开始分析软件用户的存储方式并编写针对您的软件量身定制的自定义分配方案,而不必依赖编写用于处理任何可想象的用例的完全通用的内存分配器。

仅举几例...在我成立的第一家公司中,我从事过Historian软件包的开发,该软件包负责收集/存储/存档过程控制数据(例如工厂,核电站或炼油厂,具有数百万个传感器,我们将存储该数据)。每当我们分析任何阻止Historian处理更多数据的性能瓶颈时,大多数时候问题就出在内存的处理方式上。我们已经竭尽全力确保除非绝对必要,否则不调用malloc / free。

在我目前的工作中,我从事监视视频数字记录器和分析程序包的工作。在30 fps下,每个频道每33毫秒接收一个视频帧。在我们出售的硬件上,我们可以轻松录制100个频道的视频。因此,这是确保在关键路径(网络调用=>捕获组件=>记录器管理软件=>存储组件=>磁盘)中没有任何动态内存分配的另一种情况。我们有一个自定义帧分配器,其中包含固定大小的缓冲区存储区,并使用LIFO重用先前分配的缓冲区。如果您需要600Kb的存储空间,则可能会得到1024Kb的缓冲区,这会浪费空间,但是由于它是专为我们的用途而量身定制的,因此每次分配的时间都很短,因此可以很好地工作,因为使用了缓冲区,

在我所描述的应用程序类型中(将大量数据从A移到B并处理大量的客户端请求)是往堆后移,这是CPU性能瓶颈的主要来源。将堆碎片保持在最低水平是第二个好处,但是据我所知,大多数现代OS已经实现了低碎片堆(至少我知道Windows可以,并且希望其他人也这样做)。就个人而言,在这些类型的环境中工作了12年以上,我经常看到与堆有关的CPU使用率问题,而我从未见过实际遭受过零散堆的系统的问题。


“我们已经竭尽全力确保除非绝对必要,否则不会调用malloc / free……” -我知道一些制造路由器的硬件人员。他们甚至不理会malloc/free。它们保留一块内存并将其用作游标数据结构。他们的大部分工作都减少到跟踪索引。

4

视频处理,VFX,操作系统等。尽管如此,人们经常会过度使用它们。数据结构和分配器无需分开即可实现有效分配。

例如,它引入了很多额外的复杂性,以使octree中的有效树节点分配与octree本身分离,并依赖于外部分配​​器。将这两个方面融合在一起并使其由octree负责一次连续分配多个节点并不一定要违反SRP,因为这样做不会增加更改原因的数量。实际上,它可以减少它。

例如,在C ++中,使标准容器依赖于外部分配​​器的滞后副作用之一使链接结构像C ++社区一样,std::mapstd::list几乎被C ++社区认为是无用的,因为它们将它们作为基准。std::allocator而这些数据结构一次分配一个节点。当然,在这种情况下,链接结构的性能会很差,但是如果将有效地分配给链接结构的节点的分配视为数据结构而不是分配者的职责,情况会大为不同。他们可能出于其他原因(例如内存跟踪/分析)仍然使用自定义分配,但是在尝试一次分配节点时,依靠分配器使链接结构高效,所有这些默认情况下效率极低,如果它带有一个众所周知的警告,那就好了,链接结构现在需要自定义分配器(如自由列表),以使其具有合理的效率并避免左右触发高速缓存未命中。实际上更适用的可能是std::list<T, BlockSize, Alloc>,其中BlockSize表示要立即为空闲列表分配的连续节点的数量(指定1会导致std::list现在有效)。

但是没有这样的警告,然后导致整个黑社会都回荡着邪教的口头禅,那就是链表是无用的,例如


3

您可能需要自定义分配器的另一个领域是防止堆碎片化。随着时间的流逝,您的堆可能会分配分散在整个堆中的小对象。如果您的程序无法将堆内存保持在一起,那么当您的程序要分配更大的对象时,它必须从系统中请求更多的内存,因为它无法在现有的碎片堆之间找到可用的块(太多的小块)物体在挡)。程序的总内存使用量将随着时间增加,并且不必要地消耗更多的内存页面。因此,对于希望长时间运行的程序(例如数据库,服务器等)来说,这是一个很大的问题。

除游戏外,在哪里大量使用自定义分配器?

脸书

查看jemalloc,Facebook正在开始使用它们来提高其堆性能并减少碎片。


对。但是,复制垃圾收集器可以很好地解决碎片问题,不是吗?
rwallace 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.