供应商是否将 new 和 malloc 实现为小对象分配器?
do vendors implement new and malloc as small object allocators?
是否允许实现 new
and/or malloc
分配比请求多得多的内存,从而避免以后小分配的开销?
根据我的经验,由于成本高昂,没有人会在堆上分配单个对象,通常是编写小型对象分配器或尽可能简单地创建大型数组。因此,为程序员执行此操作的实现感觉应该是一个易于实现的 ergonomics/performance 功能。
编译器是否已经这样做了,或者标准或其他问题是否阻止了这一点?
Do compilers already do this, or does the standard or another issue prevent this?
该标准不会阻止分配函数分配超过请求的数量。它只说明成功分配意味着分配的内存至少与请求的大小一样大。
引用 C++ 标准 (n4659),
6.7.4.1 Allocation functions [basic.stc.dynamic.allocation]
...
2. The allocation function attempts to allocate the requested amount of storage. If it is successful, it shall return the address of the start of a block of storage whose length in bytes shall be at least as large as the requested size.
大多数操作系统 [需要引用] 以块的形式管理内存,通常称为 "pages"。这是底层硬件的产物。
长期以来,图书馆的 malloc
(以及 new
)将通过分配一个或多个 "pages" 内存来满足用户对内存的请求从操作系统,然后将该内存分配给用户。(*) 无需从 OS 请求更多页面即可满足的后续请求将以这种方式得到满足。
血淋淋的细节因系统和分配器而异。他们通常试图在速度(分配/释放)和效率(最小内存使用)之间取得平衡。
也传统的是有特定内存要求(速度,效率)的应用程序一次性malloc
一大块内存,然后做管理自己的记忆。这增加了复杂性并增加了错误的机会(例如,通过应用程序的内存管理分配内存但 free()
d,或内存 malloc()
ed 但通过应用程序内存管理释放,或应用程序内存管理本身的错误),但允许应用程序控制所使用的算法。
C++ 通过 allocators 使这更容易,这有效地 "outsource" 容器的内存管理到不同的 class,允许使用定制的、可重复使用的内存管理 classes.
所以:
- 是的,这是可能的。
- 不,标准中没有任何内容禁止它。
- 是的,这通常已经完成了 "under the hood"。
3. 的推论当然是老生常谈测量、优化、测量。(不要试图优化你没有的问题,并且如果这样做,请确保您的优化确实改善了事情而不是使事情变得更糟。)
(*) 引入 "pages" 概念的硬件与确实保护独立应用程序的内存空间免受彼此影响的硬件相同 -- Memory Management Unit。为避免应用程序破坏该保护,只允许操作系统修改内存分配。术语和体系结构不同,但通常有某种 "supervisor mode" 仅适用于 OS 内核,因此应用程序必须触发内核,然后进行分配,然后 returns 控制应用程序。
这称为 "context switch",就 CPU 时间而言,它是最昂贵的操作之一。所以从一开始,库的实现者就在寻找最小化上下文切换的方法。这就是为什么 malloc
和 new
通常已经针对一般用途进行了相当好的优化。
供应商(在这种情况下是 libc-implementors,因为 malloc/new 通常是由您的标准库而不是您的编译器提供商真正实现的)做各种事情,通常您可以期待某种小对象优化和最后分配大小缓存至少达到一定大小。
例如,这里是我使用 libc 的时间安排,用于 mallo-free 和 glibc Linux x86_64 和二次幂大小:
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})
与 musl-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 也有很大差异)。
如果您真的关心一些特定的小型分配,最好使用您自己的空闲列表分配器并获得最佳速度/内存使用率,而不管您的后端如何。
是否允许实现 new
and/or malloc
分配比请求多得多的内存,从而避免以后小分配的开销?
根据我的经验,由于成本高昂,没有人会在堆上分配单个对象,通常是编写小型对象分配器或尽可能简单地创建大型数组。因此,为程序员执行此操作的实现感觉应该是一个易于实现的 ergonomics/performance 功能。
编译器是否已经这样做了,或者标准或其他问题是否阻止了这一点?
Do compilers already do this, or does the standard or another issue prevent this?
该标准不会阻止分配函数分配超过请求的数量。它只说明成功分配意味着分配的内存至少与请求的大小一样大。
引用 C++ 标准 (n4659),
6.7.4.1 Allocation functions [basic.stc.dynamic.allocation]
...
2. The allocation function attempts to allocate the requested amount of storage. If it is successful, it shall return the address of the start of a block of storage whose length in bytes shall be at least as large as the requested size.
大多数操作系统 [需要引用] 以块的形式管理内存,通常称为 "pages"。这是底层硬件的产物。
长期以来,图书馆的 malloc
(以及 new
)将通过分配一个或多个 "pages" 内存来满足用户对内存的请求从操作系统,然后将该内存分配给用户。(*) 无需从 OS 请求更多页面即可满足的后续请求将以这种方式得到满足。
血淋淋的细节因系统和分配器而异。他们通常试图在速度(分配/释放)和效率(最小内存使用)之间取得平衡。
也传统的是有特定内存要求(速度,效率)的应用程序一次性malloc
一大块内存,然后做管理自己的记忆。这增加了复杂性并增加了错误的机会(例如,通过应用程序的内存管理分配内存但 free()
d,或内存 malloc()
ed 但通过应用程序内存管理释放,或应用程序内存管理本身的错误),但允许应用程序控制所使用的算法。
C++ 通过 allocators 使这更容易,这有效地 "outsource" 容器的内存管理到不同的 class,允许使用定制的、可重复使用的内存管理 classes.
所以:
- 是的,这是可能的。
- 不,标准中没有任何内容禁止它。
- 是的,这通常已经完成了 "under the hood"。
3. 的推论当然是老生常谈测量、优化、测量。(不要试图优化你没有的问题,并且如果这样做,请确保您的优化确实改善了事情而不是使事情变得更糟。)
(*) 引入 "pages" 概念的硬件与确实保护独立应用程序的内存空间免受彼此影响的硬件相同 -- Memory Management Unit。为避免应用程序破坏该保护,只允许操作系统修改内存分配。术语和体系结构不同,但通常有某种 "supervisor mode" 仅适用于 OS 内核,因此应用程序必须触发内核,然后进行分配,然后 returns 控制应用程序。
这称为 "context switch",就 CPU 时间而言,它是最昂贵的操作之一。所以从一开始,库的实现者就在寻找最小化上下文切换的方法。这就是为什么 malloc
和 new
通常已经针对一般用途进行了相当好的优化。
供应商(在这种情况下是 libc-implementors,因为 malloc/new 通常是由您的标准库而不是您的编译器提供商真正实现的)做各种事情,通常您可以期待某种小对象优化和最后分配大小缓存至少达到一定大小。
例如,这里是我使用 libc 的时间安排,用于 mallo-free 和 glibc Linux x86_64 和二次幂大小:
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})
与 musl-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 也有很大差异)。
如果您真的关心一些特定的小型分配,最好使用您自己的空闲列表分配器并获得最佳速度/内存使用率,而不管您的后端如何。