Hacker News 中文摘要

RSS订阅

用“无用”的if语句将代码性能提升四倍 -- Quadrupling code performance with a "useless" if

文章摘要

文章介绍了一个通过添加看似无用的if语句,将代码性能提升四倍的优化案例,核心在于利用条件分支避免不必要的计算,从而大幅提高运行效率。

文章总结

用“无用”的if语句将代码性能提升四倍

在优化领域特定压缩器时,我遇到了一个关键问题:需要对输入字符串进行分块,并为每个块选择最紧凑的编码方式。算法核心是在网格上寻找最短路径,通过计算每个单元格的最佳后续单元格来确定最优编码顺序。

延迟问题

第二个循环看起来很简单,核心操作只是 j = next_j[i][j],编译后仅是一条 mov 指令。但现代处理器具有指令级并行能力,可以同时执行多条指令。然而,由于 j 在循环迭代间存在数据依赖,每次迭代必须等待前一次完成,这导致性能受限于内存访问延迟。

分支预测优化

虽然无法直接控制地址预测,但可以通过分支预测来模拟。如果 j 保持不变的概率很高,我们可以让CPU预测 j 不变:

for (int i = 0; i < n_symbols; i++) { if (j != next_j[i][j]) { j = next_j[i][j]; } encoding[i] = j; }

当CPU预测 if 体不太可能执行时,它会忽略依赖关系,允许不同迭代并行执行。只有当条件为真时,才会触发分支误预测恢复机制。

编译器技巧

问题在于编译器认为这个 if 是多余的,会通过公共子表达式消除将其移除。解决方案是使用 volatile 强制编译器认为条件和赋值是独立的:

for (int i = 0; i < n_symbols; i++) { if (j != next_j[i][j]) { j = *(uint8_t volatile *)&next_j[i][j]; } encoding[i] = j; }

在合成基准测试中,这个改动将循环时间从320微秒降至80微秒。在实际测试中,由于LLVM的代码生成问题,性能提升约为2倍,但仍然值得采用。

评论总结

根据评论内容,主要围绕一项性能优化技术展开讨论,核心观点如下:

1. 技术新颖性与有效性(高认可度) - 评论1:"Brilliant! Hadn't seen this technique before."(精彩!从未见过这种技术。) - 评论3:"This is really surprising! I've never considered the possibility that using an equality test to skip a write...could break a dependency"(令人惊讶!从未想过用相等测试跳过无操作写入能打破依赖关系)

2. 优化原理与适用场景(中高认可度) - 评论5:"adding the branch allows the branch prediction to parallelise what it otherwise couldn't"(添加分支让分支预测器能并行化原本无法并行化的操作) - 评论3:"applicable in many situations where you 'edit' some data in-place, but most of the time there are few or no changes"(适用于原地编辑数据但多数情况无变化的场景)

3. 替代方案与工具支持(中等认可度) - 评论4建议使用"_builtinexpect"或"likely()/unlikely()"宏,认为"better than inserting obscure optimisations"(比插入晦涩优化更好) - 评论6指出"[[unlikely]] works here for clang"(Clang的[[unlikely]]属性在此有效)

4. 实践验证与扩展(中等认可度) - 评论9分享类似优化经验:"letting the compiler know that a data dependency occurs very infrequently...has allowed me to speed up my decompression"(让编译器知道数据依赖极少发生,显著加速解压) - 评论10提出替代方案:通过读取8字节整块数据"remove dependency on previous iteration"(消除对前次迭代的依赖)

争议点:部分评论认为添加分支可能违反现代CPU优化原则(避免分支),但实际效果证明分支预测能带来更大收益。