Matt Godbolt's blog16 hours agohttp://xania.org/202512/08-going-loopy?utm_source=feed&utm_medium=rss关于C++最佳循环风格的辩论导致了2011年编译器浏览器的诞生。不同的循环风格如for()、while()、range-for和STL算法通常编译成相同的优化汇编代码。范围for循环和基于指针的循环产生相同的高效代码,避免不必要的尺寸计算。建议使用标准算法或范围for循环以提高清晰度,因为编译器会将它们规范化成最优的底层模式。基于序号的循环风格可能会产生轻微的低效,但现代编译器通常能很好地优化它们。
Matt Godbolt's blog16 hours agohttp://xania.org/202512/11-pop-goes-the-weasel-er-count?utm_source=feed&utm_medi...
Matt Godbolt's blog16 hours agohttp://xania.org/202512/12-loop-unswitching?utm_source=feed&utm_medium=rss编译器通过为不同条件复制循环来优化循环,这被称为“循环分离”。在较低的优化级别(如 -O2)下,编译器可能会使用像乘加运算这样的条件指令来避免循环内的分支。循环分离将不变性检查移到循环外部,并创建专门化的循环副本,以代码大小换取性能。更高的优化级别启用循环分离,当条件保持不变时消除不必要的操作(例如乘法)。信任编译器的优化决策,但要理解其中的权衡,可以通过像 Compiler Explorer 这样的工具进行验证。
Matt Godbolt's blog16 hours agohttp://xania.org/202512/13-licking-licm?utm_source=feed&utm_medium=rss编译器通过将循环外不变的代码(如大小计算)移出循环来优化循环,这被称为循环不变代码移动(LICM)。在一个string_view计数示例中,clang将get_range()调用移出循环,并使用setle/setge来避免分支。相信编译器,但使用Compiler Explorer等工具验证其输出,因为gcc可能无法对某些结构类型执行LICM。
Matt Godbolt's blog16 hours agohttp://xania.org/202512/14-licm-when-it-doesnt?utm_source=feed&utm_medium=rss循环不变代码外提(LICM)可以将像`strlen`这样的计算从循环中提取出来,以生成高效的汇编代码。在循环内部修改全局变量(例如`num_compares`)可能导致LICM失败。失败的原因是别名问题:`char*`被允许与任何类型别名,因此当全局变量被修改时,编译器无法证明字符串未改变。尽管有严格的别名规则,`char*`(以及`unsigned char*`、`std::byte*`)有一个特殊例外,允许它们与任何东西别名。在实践中,GCC、Clang和MSVC都无法对修改后的代码执行LICM。下一篇文章将探讨更多的别名问题以及如何帮助编译器优化此类代码。
Matt Godbolt's blog16 hours agohttp://xania.org/202512/15-aliasing-in-general?utm_source=feed&utm_medium=rss
Matt Godbolt's blog16 hours agohttp://xania.org/202512/16-calling-conventions?utm_source=feed&utm_medium=rss调用约定是ABI的一部分,因架构和操作系统而异,其中x86 Linux上的System V ABI被认为是最合理的。在x86 Linux上,前两个参数(包括结构体)通过寄存器rdi和rsi传递,这通常允许高效地传递小型结构体。对于包含多个整数类型(例如int)的结构体,编译器可能会通过额外的指令将它们打包到一个寄存器中,而对于许多参数(超过6个),则会发生栈溢出。更改参数类型(例如改为char)可以避免栈溢出,如果整个结构体能放入一个寄存器,不过编译器可能会使用不同的优化技巧。理解ABI有助于设计像std::string_view和std::optional这样的类型,以高效地通过值传递,利用寄存器避免栈开销。
Matt Godbolt's blog16 hours agohttp://xania.org/202512/17-inlining-the-ultimate-optimisation?utm_source=feed&ut...内联是一种基本的编译器优化,它将函数体复制到调用点,从而实现诸如常量传播和死代码消除等进一步优化。内联可以减少调用开销,但其主要好处是允许编译器在内联点对代码进行特化,例如基于已知常量简化分支。内联的缺点包括增加代码大小,这可能会对指令缓存造成压力,以及不同编译器之间复杂的启发式决策。内联需要函数定义的可见性;声明没有函数体的函数会阻止内联,尽管链接时优化(LTO)可以在翻译单元之间提供帮助。内联的有效性取决于上下文,对于分支预测和性能存在微妙的权衡,编译器必须在没有运行时信息的情况下进行猜测。
Matt Godbolt's blog16 hours agohttp://xania.org/202512/20-simd-city?utm_source=feed&utm_medium=rssSIMD允许CPU用单条指令处理多个数据点,从而提升数组密集型计算的性能。编译器可通过-O3和-march=skylake等标志自动向量化循环,但需要合适的数据布局(例如整数数组)。条件更新(如取最大值)可使用掩码移动向量化,或改写为无条件的操作,映射到vpmaxsd等SIMD指令。编译器会添加数组重叠的运行时检查以确保向量化正确性,通常会生成一个回退的标量循环。
Matt Godbolt's blog16 hours agohttp://xania.org/202512/22-memory-cunningness?utm_source=feed&utm_medium=rss编译器通过重叠内存读取和按位异或操作,优化字符串与编译时常量的比较。对于不同长度的常量字符串,编译器内联比较,并使用高效技术,如先检查长度,然后执行字节或字级比较。比较7个字符的字符串时,使用两次重叠的32位读取和异或操作,避免逐字节检查,利用相同值异或为零的特性。现代编译器(如Clang)会自动从简单代码(如`sv == "ABCDEFG"sv`)应用此类优化(如无分支条件、未对齐读取),使开发者能专注于代码清晰性。编译器之间存在差异:例如,对于相似的字符串比较,GCC可能生成更直接但优化较少的代码,而Clang则更高效。
Matt Godbolt's blog16 hours agohttp://xania.org/202512/24-cunning-clang?utm_source=feed&utm_medium=rssGCC 通过使用 LEA 指令一次处理两个数来优化简单的求和循环,实现了 O(n) 复杂度,但迭代次数更少。Clang 更进一步,识别出整数求和公式,并将循环替换为 O(1) 的封闭形式表达式:v(v-1)/2。作者指出,数十年的编译器开发仍能产生令人惊讶且优雅的优化,凸显了现代编译器的精妙之处。
When Compilers Disagree About UTF‑82 days agohttps://nemanjatrifunovic.substack.com/p/when-compilers-disagree-about-utf8一个用于处理UTF-8的开源C++库最初创建时没有进行大量优化。优化集中在ASCII快捷方式:ASCII字符(范围U+0000–U+007F)始终通过UTF-8有效性检查,允许提前返回。一个简单的ASCII快速路径使Clang在纯ASCII文本上的性能提升了三倍,在混合文本上提升了34%。对于相同的优化,GCC在纯ASCII文本上没有变化,而在混合文本上性能下降了3-4%。分析表明,Clang没有消除解码后的检查,而GCC已经优化掉了这些检查。一种改进的方法(在解码函数中内联验证)提高了两个编译器的性能:保留了Clang的增益,并在GCC混合文本上增加了15-20%的提升。教训:编译器行为各异;仔细优化可以带来显著收益。
Dense Arena Interning: The Engine of Compiler Performance16 days agohttps://aikoschurmann.com/blog/string-interning-compilers通过使用稠密竞技场驻留技术减少重复的字符串和结构检查,从而提升编译器性能。稠密竞技场驻留在词法分析或类型构建过程中将字符串和结构转换为稠密整数,使得在后续编译阶段能够实现 O(1) 复杂度的查找与比较。竞技场分配器提供稳定的内存空间,允许基于指针的比较和高效的内存管理,而无需重新分配内存。稠密 ID 支持扁平化的数组符号表,提升缓存局部性,并将符号解析转变为 O(1) 复杂度的数组索引操作。驻留技术将复杂类型规范化,允许通过浅层指针比较替代深层结构检查,从而进一步加快类型与符号比较的速度。
Your code is fast – if you're lucky17 days agohttps://tiki.li/blog/lucky_code.html现代编译器,特别是 Clang,在使用某些编程风格时,会通过无分支指令来优化循环。一个采用了排序网络和循环展开的快速排序实现,最初比 C++ 标准库的 std::sort 慢。将一段初学者友好的 if 语句改写为紧凑的 C 语言惯用法(使用后置递增运算符)显著提高了性能。这个“外观上”的改动触发 Clang 将分支替换为 csel(条件选择)指令,使代码变为无分支结构。在 x86 架构上,Clang 类似地使用 cmov 指令生成无分支代码,而 GCC 则始终生成较慢的基于分支的版本。优化后的快速排序比原始版本快了超过 6 倍,而且比 std::sort 也快了近一倍。
The mask that compiles to nothing: how HotSpots JIT learned to reason about bits23 days agohttps://questdb.com/blog/jvm-jit-known-bits/HotSpot的C2编译器通过追踪已知位与范围来优化位运算。通过证明位未改变,该优化消除了冗余的位与操作,例如移除(x << 2) & -4。系统为每个整数使用两个32位掩码:零位和一位来表示确定的位状态。范围和已知位通过规范化过程相互优化彼此。为AND、OR和移位等操作定义了位传播规则,实现精确分析。该功能在JDK 26中实现并在JDK 27中改进,测试方法确保了正确性。