默认new运算符与std::malloc的性能差异探究
问题:重载
new/delete为std::malloc/std::free的模板类性能优于原生类的原因 我用Google Benchmark测试时,实现了一个将new/delete重载为std::malloc/std::free的模板类Standard<T>,测试发现Standard<T>的性能略优于原生类T。按道理默认的new/delete应该是基于std::malloc/std::free实现的,为什么会存在性能差异?
核心重载代码:
template <typename Derived> class Standard { public: static void* operator new(std::size_t size) { return std::malloc(size); } static void operator delete(void *ptr, std::size_t) { std::free(ptr); } };
完整可编译测试代码:
#include "evelyn/PoolPolicy.h" #include <cstdint> #include <benchmark/benchmark.h> template <size_t N> struct Block { int8_t data[N]; }; template <typename T> class Standard { public: static void *operator new(std::size_t size) { return std::malloc(size); } static void operator delete(void *ptr, std::size_t) { std::free(ptr); } }; using A = Standard<int>; using B = int; class PoolFixture : public benchmark::Fixture { public: void SetUp(const ::benchmark::State &state) { } void TearDown(const ::benchmark::State &state) { } }; auto constexpr ITERATE_TIME = 1000; BENCHMARK_F(PoolFixture, without_pool_only_allocate) (benchmark::State &state) { using Block = Block<10>; for (auto _ : state) { for (auto i = 0; i < ITERATE_TIME; ++i) { auto ptr = new Block; } } } BENCHMARK_F(PoolFixture, with_pool_only_allocate) (benchmark::State &state) { using Block = Standard<Block<10>>; for (auto _ : state) { for (auto i = 0; i < ITERATE_TIME; ++i) { auto ptr = new Block; } } } BENCHMARK_F(PoolFixture, without_pool_allocate_and_delete_1) (benchmark::State &state) { using Block = Block<10>; for (auto _ : state) { for (auto i = 0; i < ITERATE_TIME; ++i) { auto ptr = new Block; delete ptr; } } } BENCHMARK_F(PoolFixture, with_pool_allocate_and_delete_1) (benchmark::State &state) { using Block = Standard<Block<10>>; for (auto _ : state) { for (auto i = 0; i < ITERATE_TIME; ++i) { auto ptr = new Block; delete ptr; } } }
可能的原因分析
默认
new/delete的额外操作:标准仅要求默认new/delete最终调用malloc/free,但允许实现添加额外逻辑。比如:- 默认
new在分配失败时会抛出std::bad_alloc异常,而你的重载直接返回nullptr,节省了异常处理的开销; - 部分编译器的默认
new会加入轻量内存检查、对齐校验或分配统计逻辑(即使Release模式下也可能存在),你的重载完全跳过了这些步骤。
- 默认
编译器优化差异:
- 自定义重载的逻辑更简单,编译器更容易做内联或激进优化;默认实现因包含更多分支(如异常处理、线程安全检查),优化难度更高;
- 对于原生类型或简单结构体,编译器对默认
new可能有特殊处理逻辑,而你的重载模板让编译器将其视为普通类分配,触发了更高效的优化路径。
线程安全的细微差异:
- 虽然
malloc/free本身是线程安全的,但默认new/delete可能做了额外的线程安全封装(比如更粗粒度的锁),而你的重载直接调用malloc/free,刚好匹配测试场景的线程模型,减少了锁开销。
- 虽然
编译模式与选项影响:
- 如果测试未在纯净Release模式下运行,默认
new/delete可能残留调试钩子(如_CRTDBG_MAP_ALLOC),而你的重载不受这些钩子影响; - 不同编译优化选项(如
-O2/-O3)对默认实现和自定义重载的优化效果不同,比如默认实现的某些分支被编译器保留,而你的重载被完全优化为直接调用malloc/free。
- 如果测试未在纯净Release模式下运行,默认
内存对齐与块大小差异:
- 默认
new可能为了满足严格对齐或内存池块大小要求,分配比实际需求更大的内存;而你的重载直接按传入size调用malloc,返回的内存刚好满足最小对齐要求,间接提升了缓存命中率,进而优化性能。
- 默认
内容的提问来源于stack exchange,提问作者Junhui Zhu
相关产品推荐
相关产品推荐

