两次堆分配开销是否高于std::string填充构造函数调用?
性能对比结论
绝大多数主流编译环境下,第二种实现的运行速度更快、效率更高。
两种实现的开销拆解
第一种实现的额外无用开销
你对第一种实现的判断基本准确,但忽略了131字节初始化填0的成本:
- 构造时确实只会触发1次堆内存分配(131字节超过了主流编译器默认15/16字节的小字符串优化(SSO)阈值,无法使用栈上内置缓冲区)
- 但构造时强制把131字节全部填0的操作是完全无用的,后续clear只是把字符串的
size标记为0,已经填的0不会被用到,属于纯冗余的O(n)操作,哪怕n只有131,也会产生不必要的缓存写入开销。
第二种实现的实际开销
你之前担心的「2次内存分配」其实不存在:
- 默认构造std::string时,主流编译器都会直接使用栈上的SSO缓冲区,不会触发任何堆内存分配,仅会初始化
size=0、capacity=SSO阈值两个字段,开销几乎可以忽略 - 调用
reserve(131)时才会触发1次堆内存分配,预留131字节的缓冲区,且reserve仅分配内存、不会对内存做任何初始化填充,没有额外的写入开销 - 全程仅1次堆分配,没有冗余的内存初始化操作,开销比第一种低。
仅在极少数关闭了SSO的特殊编译环境下,默认构造会分配16字节堆内存、reserve时再分配131字节堆内存,才会出现2次堆分配的情况,这种场景下需要实际测试对比1次额外堆分配和131字节填0的开销,但这类场景几乎不会在生产环境出现。
更优的实现方式
如果使用C++20及以上版本,可以用resize_and_overwrite进一步减少开销:
std::string tempMsg; tempMsg.resize_and_overwrite(131, [](char* buf, size_t buf_size) { // 直接往buf写入你需要的内容,不需要再走append接口 size_t actual_used = snprintf(buf, buf_size, "%s", "/* some string literals */"); // 返回实际写入的字符数,会作为最终的string size return actual_used; });
这个接口会直接分配131字节的缓冲区,不需要初始化填0,也不需要后续的append调用,直接在原始缓冲区上操作,开销是最低的。
如果是C++11/17环境,你写的第二种实现就是当前最优的方案。
内容的提问来源于stack exchange,提问作者digito_evo
相关产品推荐
相关产品推荐

