供应商是否将 new 和 malloc 实现为小型对象分配器

do vendors implement new and malloc as small object allocators?

本文关键字:小型 对象 分配器 实现 malloc 是否 new 供应商      更新时间:2023-10-16

是否允许实现具有new和/或malloc分配比请求的内存多得多的内存,以便避免以后少量分配的开销?

根据我的经验,没有人会在堆上分配单个对象,因为它的成本很高,通常编写小对象分配器或在可能的情况下简单地创建大型数组。 因此,为程序员执行此操作的实现感觉应该是一个简单的人体工程学/性能功能。

编译器是否已经这样做了,或者标准或其他问题是否阻止了这一点?

大多数操作系统 [需要引用] 以块的形式管理内存,通常称为"页面"。这是底层硬件的工件。

长期以来的做法是,库的malloc(以及扩展的new)将通过从操作系统分配一个或多个内存"页"来满足用户的内存请求,然后将该内存分配给用户。(*)无需从操作系统请求更多页面即可满足的后续请求将以这种方式满足。

血腥的细节因系统和分配器而异。他们通常试图在速度(分配/释放)和效率(最小内存使用)之间取得平衡。

同样传统的是,具有特定内存要求(速度、效率)的应用程序一次性malloc一大块内存,然后自行管理该内存。这增加了复杂性和更多错误的机会(例如,通过应用程序的内存管理分配但free()d,内存malloc()编辑但通过应用程序内存管理释放,或应用程序内存管理本身的错误),但允许应用程序控制使用的算法。

C++通过分配器使这更容易,分配器有效地将容器的内存管理"外包"到不同的类,允许使用定制的、可重用的内存管理类。

所以:

  1. 是的,这是可能的。
  2. 不,标准中没有任何内容禁止它。
  3. 是的,这通常已经在"引擎盖下"完成。

推论 3. 当然,是老生常谈的度量,优化,度量。 (不要试图优化你没有的问题,如果你这样做了,请确保你的优化实际上改善了事情,而不是让事情变得更糟。


(*) 引入"页面"概念的硬件与保护不同应用程序的内存空间的硬件相同 - 内存管理单元。为了避免应用程序破坏该保护,只允许操作系统修改内存分配。术语和体系结构不同,但通常存在某种仅适用于操作系统内核的"主管模式",因此应用程序必须触发内核,然后内核执行分配,然后将控制权返回给应用程序。

这称为"上下文切换",就 CPU 时间而言,它是最昂贵的操作之一。因此,从一开始,库实现者就在寻找最小化上下文切换的方法。这就是为什么mallocnew通常已经针对一般用途进行了很好的优化。

编译器是否已经这样做了,或者标准或其他问题是否阻止了这一点?

该标准不会阻止分配函数分配超过请求的分配。它仅声明成功分配意味着分配的内存将至少与请求的大小一样大。

引用C++标准 (n4659),

6.7.4.1 分配函数 [基本.stc.动态.分配]
...
2. 分配函数尝试分配请求的存储量。如果成功,它应返回存储块的起始地址,该存储块的长度(以字节为单位)应至少与请求的大小一样大。

供应商(在这种情况下是libc-implementors,因为malloc/new通常是由你的标准库而不是编译器提供者实现的)做各种事情,通常你可以期待某种小对象优化和最后分配大小缓存至少达到一定大小。

例如,以下是我在 Linux x86_64 上使用 glibc 的 libc 和 mallo-free 和 2 次方大小的时间:

15.61 ns(R)     15.56 ns(U)     0.04 ns(S)      (2050002iters; 32 ms)   (malloc_free_)(&($_sz){1ULL<<3})
16.29 ns(R)     16.27 ns(U)     0.01 ns(S)      (1964070iters; 32 ms)   (malloc_free_)(&($_sz){1ULL<<4})
16.31 ns(R)     16.29 ns(U)     0.00 ns(S)      (1962244iters; 32 ms)   (malloc_free_)(&($_sz){1ULL<<5})
18.17 ns(R)     18.15 ns(U)     0.00 ns(S)      (1761118iters; 32 ms)   (malloc_free_)(&($_sz){1ULL<<6})
16.42 ns(R)     16.41 ns(U)     0.00 ns(S)      (1949061iters; 32 ms)   (malloc_free_)(&($_sz){1ULL<<7})
15.97 ns(R)     15.96 ns(U)     0.00 ns(S)      (2003412iters; 32 ms)   (malloc_free_)(&($_sz){1ULL<<8})
16.14 ns(R)     16.14 ns(U)     0.00 ns(S)      (1982292iters; 32 ms)   (malloc_free_)(&($_sz){1ULL<<9})
16.80 ns(R)     16.79 ns(U)     0.00 ns(S)      (1905223iters; 32 ms)   (malloc_free_)(&($_sz){1ULL<<10})
42.19 ns(R)     42.17 ns(U)     0.00 ns(S)      (758535iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<11})
42.90 ns(R)     42.88 ns(U)     0.00 ns(S)      (746074iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<12})
42.85 ns(R)     42.84 ns(U)     0.00 ns(S)      (746926iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<13})
42.32 ns(R)     42.18 ns(U)     0.00 ns(S)      (756378iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<14})
42.59 ns(R)     42.55 ns(U)     0.00 ns(S)      (751520iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<15})
41.98 ns(R)     41.97 ns(U)     0.00 ns(S)      (762451iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<16})
42.74 ns(R)     42.72 ns(U)     0.00 ns(S)      (748953iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<17})
42.32 ns(R)     42.31 ns(U)     0.00 ns(S)      (756267iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<18})
41.99 ns(R)     41.98 ns(U)     0.00 ns(S)      (762255iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<19})
42.31 ns(R)     42.30 ns(U)     0.00 ns(S)      (756442iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<20})
51.03 ns(R)     50.17 ns(U)     0.00 ns(S)      (627259iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<21})
44.93 ns(R)     44.91 ns(U)     0.00 ns(S)      (712362iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<23})
6674.43 ns(R)   677.29 ns(U)    5813.42 ns(S)   (4797iters; 32 ms)      (malloc_free_)(&($_sz){1ULL<<25})

穆斯林-libc 也是如此:

64.09 ns(R)     64.07 ns(U)     0.00 ns(S)      (499411iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<3})
61.49 ns(R)     61.47 ns(U)     0.00 ns(S)      (520542iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<4})
62.67 ns(R)     62.64 ns(U)     0.00 ns(S)      (510794iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<5})
61.53 ns(R)     61.52 ns(U)     0.00 ns(S)      (520150iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<6})
61.49 ns(R)     61.47 ns(U)     0.00 ns(S)      (520514iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<7})
62.78 ns(R)     62.66 ns(U)     0.00 ns(S)      (509871iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<8})
61.38 ns(R)     61.36 ns(U)     0.00 ns(S)      (521468iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<9})
79.97 ns(R)     79.94 ns(U)     0.00 ns(S)      (400374iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<10})
68.77 ns(R)     68.72 ns(U)     0.00 ns(S)      (465530iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<11})
68.21 ns(R)     68.18 ns(U)     0.00 ns(S)      (469345iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<12})
76.55 ns(R)     76.39 ns(U)     0.00 ns(S)      (418194iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<13})
74.67 ns(R)     74.63 ns(U)     0.00 ns(S)      (428704iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<14})
63.95 ns(R)     63.94 ns(U)     0.00 ns(S)      (500507iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<15})
66.33 ns(R)     66.31 ns(U)     0.00 ns(S)      (482528iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<16})
2629.39 ns(R)   653.03 ns(U)    1975.27 ns(S)   (12174iters; 32 ms)     (malloc_free_)(&($_sz){1ULL<<17})
5776.54 ns(R)   0.00 ns(U)      5474.55 ns(S)   (5542iters; 32 ms)      (malloc_free_)(&($_sz){1ULL<<18})
6198.86 ns(R)   708.22 ns(U)    4847.24 ns(S)   (5165iters; 32 ms)      (malloc_free_)(&($_sz){1ULL<<19})
6173.01 ns(R)   379.67 ns(U)    5279.59 ns(S)   (5186iters; 32 ms)      (malloc_free_)(&($_sz){1ULL<<20})
7029.97 ns(R)   0.00 ns(U)      6224.80 ns(S)   (4555iters; 32 ms)      (malloc_free_)(&($_sz){1ULL<<21})
7050.58 ns(R)   1589.07 ns(U)   4757.32 ns(S)   (4541iters; 32 ms)      (malloc_free_)(&($_sz){1ULL<<23})
7584.38 ns(R)   0.00 ns(U)      6807.15 ns(S)   (4221iters; 32 ms)      (malloc_free_)(&($_sz){1ULL<<25})

如您所见,小尺寸在一定程度上进行了优化,但在不同的实现上有所不同(多线程 malloc-free 也有很大差异)。

如果你真的关心一些特定的小尺寸分配,那么最好使用你自己的免费列表分配器,并获得最佳的速度/内存使用,而不管你的后端如何。