性能提示(Performance Hints)

性能提示(Performance Hints)

作者:Jeff Dean, Sanjay Ghemawat

版本:2023年7月27日初版,2025年12月16日更新
https://abseil.io/fast/hints.html

性能思考的重要性

本部分强调了性能优化的重要意义,引用了Knuth的经典论断"过早优化是万恶之源"的完整语境,指出虽然不应过度关注微小效率,但关键3%的性能优化绝不可忽视。性能优化不仅仅是事后调优,而应贯穿开发全过程。忽视性能可能导致代码中无明显热点,难以定位瓶颈;库开发者如果不考虑性能,使用者难以改进;重构成本高昂;且可能引发资源过度配置等问题。因此建议在不显著增加代码复杂度的情况下,优先选择性能较好的方案。


性能估算

估算性能有助于权衡复杂度和效率。根据代码类型的不同,重点关注算法的渐进复杂度(测试代码)、代码是否处于热路径(应用代码)、以及库代码的通用性能。简单的"信封背面"估算法包括:

  1. 估算各种低级操作次数(磁盘寻址、网络往返、内存读取等)。
  2. 按操作成本加权求和。
  3. 对于延迟,考虑并发和成本重叠。

文中附有最新的低级操作时间估算表(如L1缓存参考0.5纳秒,SSD读取4KB约20,000纳秒等),并通过示例(如快排亿级数据、生成30张缩略图的时间估算)说明如何应用。


性能测量

性能测量是性能优化的首要工具。对陌生代码进行性能分析有助于理解代码结构。推荐工具包括Google的pprof和Linux的perf。测量建议:

  • 生产环境编译需开启调试信息和优化选项。
  • 编写微基准测试(microbenchmark)验证性能改进和防止回退,但注意微基准的局限。
  • 使用基准库输出性能计数器数据,获得更深入洞察。
  • 关注锁竞争,必要时使用支持锁竞争分析的互斥体实现。
  • 机器学习性能分析时,使用专用的ML分析工具。

针对平坦的性能剖析图(无明显热点),建议:

  • 积累多个小优化;
  • 查找调用栈顶部循环,考虑结构性改变;
  • 避免过于泛化的代码,使用更专用的实现;
  • 减少内存分配,获取分配剖析数据;
  • 使用硬件性能计数器分析缓存未命中等。

API设计考量

性能优化往往涉及数据结构和接口变更,建议将改动局限在封装边界内,减少对调用者的影响。注意API设计中:

  • 谨慎添加功能,避免无用特性增加实现成本。
  • 例如C++标准库容器保证迭代器稳定性,可能导致额外分配,非必要时应避免。

具体技术包括:

  • 批量接口:减少API调用次数和锁开销,如MemoryManager::LookupMany和ObjectStore::DeleteRefs。
  • 视图类型参数 :用std::string_viewstd::Span<T>等避免不必要复制。
  • 预分配/预计算参数:允许调用者传入已有数据,避免重复分配或计算。
  • 线程兼容与线程安全类型:根据使用场景决定同步策略,避免不必要同步开销。

算法改进

算法改进是性能提升的关键,典型手段包括:

  • 使用更高效的图结构初始化(如反向后序添加节点避免重复检查)。
  • 替换死锁检测算法,显著提升性能和可扩展性。
  • 用哈希表替代区间树降低复杂度。
  • 优化集合交集操作,使用哈希表替代排序交集。

示例中展示了多项算法层面的变更及其性能提升效果。


内存优化

内存布局和数据结构对性能有巨大影响:

  • 采用紧凑数据结构减少内存占用和缓存未命中。
  • 调整字段顺序减少内存填充,使用更小的数据类型。
  • 使用索引代替指针,提升缓存局部性。
  • 使用批量存储结构(如std::vectorabsl::flat_hash_map)减少分配开销。
  • 利用inline存储 (如absl::InlinedVector)避免小元素频繁分配。
  • 减少嵌套映射层级,使用复合键降低查找成本。
  • 使用Arena分配器减少分配和析构开销。
  • 小范围map可用数组替代,bit vector替代set。

减少内存分配

内存分配不仅消耗时间,还引发缓存行分散,增加缓存未命中。方法包括:

  • 避免不必要的分配,如静态零向量代替动态分配。
  • 预先调整容器大小,避免多次扩容。
  • 尽量移动而非复制数据。
  • 循环中复用临时对象,避免重复构造销毁。
  • 避免不必要的数据复制,存储指针或索引。
  • 复用序列化缓冲区。

避免无用工作

跳过不必要的计算是高效代码的重要策略:

  • 针对常用情况设计快速路径,避免通用代码开销。
  • 预计算昂贵信息,缓存结果。
  • 将昂贵计算移出循环,推迟计算到必要时。
  • 对频繁调用的代码,设计专用实现。
  • 优化日志调用,避免热路径中无意义的日志开销。
  • 缓存重复计算结果,减少重复工作。

代码规模考虑

代码规模影响编译时间、内存占用和CPU缓存效率。减少代码规模的方法:

  • 减少内联代码,尤其是高调用频率函数。
  • 提取复杂格式化到非内联辅助函数。
  • 减少模板实例化数量,改用普通参数。
  • 减少容器操作调用,使用批量插入。
  • 适当延迟内联,避免代码膨胀。

并行与同步

现代多核CPU支持并行执行,合理并行可显著提升性能:

  • 利用线程池等机制实现任务并行。
  • 减少锁开销,通过合并锁操作降低互斥体使用频率。
  • 缩短临界区,避免在锁内执行昂贵操作。
  • 通过分片降低锁竞争,使用分片数据结构或并发哈希表。
  • 使用SIMD指令加速批量数据处理。
  • 减少伪共享,对频繁修改数据使用缓存行对齐。

协议缓冲区(Protobuf)性能建议

虽然Protobuf方便序列化,但存在性能和空间开销:

  • 避免无谓使用,未序列化的无需用Protobuf。
  • 尽量扁平化消息层级,减少嵌套。
  • 常用字段用小字段号(1-15)减少编码长度。
  • 选用合适整数类型(int32、fixed32、sint32)平衡编码/解码效率。
  • 对重复数字字段使用packed选项。
  • 大字段使用bytes代替string,避免UTF8验证开销。
  • 使用string_type=VIEW避免拷贝。
  • 大字段考虑使用Cord类型。
  • 使用Arena减少内存分配和释放开销。
  • 避免Protobuf map字段,改用普通map或repeated。
  • 根据需求定义子集消息解析,提高效率。
  • 复用Protobuf对象减少频繁构造销毁。

C++具体建议

  • 优先使用absl::flat_hash_mapabsl::btree_map等高效容器。
  • 使用InlinedBitVector等内联小数据结构替代传统容器。
  • 使用gtl::small_mapsmall_ordered_set等缓存友好型容器。
  • 避免在热点代码中频繁使用absl::StatusStatusOr类型。
  • 利用批量操作和SIMD指令优化容器访问。
  • 避免不必要的复制和内存重新分配。

批量操作

处理批量数据往往比单个项处理更高效:

  • 利用SIMD加速哈希表匹配。
  • 一次处理多字节减少分支判断。
  • 采用GroupVarInt等格式批量编码解码整数。
  • 利用批量API减少锁和调用开销。

典型代码变更实例

文中列出多例实际CL(变更列表)展示综合应用多种技术的性能提升:

  • GPU内存分配器通过减少指针大小、连续内存、减少锁调用等提升40%性能。
  • Pathways系统多项改进提升20%吞吐量。
  • XLA编译器通过减少字符串比较、缓存数据结构、优化循环等提升约15%性能。
  • Google Meet代码优化日志处理提升50%以上性能。
  • SelectServer报警处理更换数据结构,减少分配,提升3.3倍性能。
  • In-memory索引服务通过多种技术提升3倍以上查询速度。

参考文献

文末附有丰富性能相关书籍、论文和博客推荐,如Agner Fog的《Optimizing software in C++》、Richard L. Sites的《Understanding Software Dynamics》、Jon Bentley的《Programming Pearls》等。


总结

本文全面系统地总结了软件性能优化的多方面技巧和原则,涵盖代码设计、算法优化、内存布局、并行同步、日志管理、代码大小控制及具体语言和库的使用建议。通过理论讲解与大量代码示例,帮助开发者理解并实践性能优化,提升软件整体效率和资源利用率。

相关推荐
汉克老师36 分钟前
GESP2025年12月认证C++六级真题与解析(单选题8-15)
c++·算法·二叉树·动态规划·哈夫曼编码·gesp6级·gesp六级
郝学胜-神的一滴1 小时前
线程同步:并行世界的秩序守护者
java·linux·开发语言·c++·程序人生
im_AMBER1 小时前
Leetcode 95 分割链表
数据结构·c++·笔记·学习·算法·leetcode·链表
明洞日记1 小时前
【VTK手册032】vtkImageConstantPad:医学图像边界填充与尺寸对齐
c++·图像处理·vtk·图形渲染
Aevget1 小时前
MFC扩展库BCGControlBar Pro v37.1亮点:Ribbon Bar组件全新升级
c++·ribbon·mfc·bcg·界面控件·ui开发
cchjyq1 小时前
嵌入式按键调参:简洁接口轻松调参(ADC FLASH 按键 屏幕参数显示)
c语言·c++·单片机·mcu·开源·开源软件
程序炼丹师1 小时前
std::runtime_error是否会终止程序
c++
qq_433554541 小时前
C++字符串hash
c++·算法·哈希算法
无限进步_1 小时前
【C语言】堆(Heap)的数据结构与实现:从构建到应用
c语言·数据结构·c++·后端·其他·算法·visual studio
CodeOfCC1 小时前
C++ 实现ffmpeg解析hls fmp4 EXT-X-DISCONTINUITY并支持定位
开发语言·c++·ffmpeg·音视频