为何遍历std::array时std::find生成的汇编比手写循环更慢且不同?
std::array元素存在性判断的编译优化差异分析
我编写了一段判断std::array<int, 3>元素是否存在的代码,实现了三种方式:手动下标循环、范围for循环、调用std::find:
#include <algorithm> #include <iterator> #include <array> constexpr std::array<int, 3> arr = { 0, 1, 2}; bool forLoop(int inp) { for (int i {0}; i < arr.size(); ++i) { if (arr[i] == inp) { return true; } } return false; } bool forEachLoop(int inp) { for (int i : arr) { if (i == inp) { return true; } } return false; } bool STL(int inp) { return std::find(arr.begin(), arr.end(), inp) != arr.end(); }
使用x86-64 clang 15.0.0编译器,以-std=c++17 -O3参数编译时,forLoop和forEachLoop生成的汇编代码极度精简,直接等价于判断输入是否为0、1、2中的一个:
cmp edi, 3 setb al ret
但调用std::find的STL函数生成的汇编代码则冗长很多,保留了更多分支和冗余判断:
test edi, edi je .LBB2_1 cmp edi, 1 jne .LBB2_3 lea rax, [rip + arr+4] lea rcx, [rip + arr+12] cmp rax, rcx setne al ret .LBB2_1: lea rax, [rip + arr] lea rcx, [rip + arr+12] cmp rax, rcx setne al ret .LBB2_3: xor eax, eax cmp edi, 2 setne al lea rcx, [rip + arr] lea rax, [rcx + 4*rax] add rax, 8 lea rcx, [rip + arr+12] cmp rax, rcx setne al ret
改用GCC编译时,std::find生成的汇编依然更长;但当把arr的元素个数改为4时,三个函数生成的汇编代码完全一致。
问题解答
这属于编译器的优化遗漏(missed optimization),而非功能性bug,核心原因在于通用算法的优化适配逻辑:
- 手动循环(包括范围for)的逻辑非常直白,编译器能直接推导出等价规则:输入只要是0、1、2就返回true,否则返回false,进而优化成极简的汇编指令。
std::find是通用算法,设计目标是支持所有迭代器类型(包括非随机访问迭代器),编译器需要对它做针对性的特化优化才能达到手动循环的效果。对于大小为4的数组,编译器的优化规则覆盖了该场景,能完全展开循环并消除冗余逻辑;但对于大小为3的数组,当前优化规则的适配范围没有覆盖到,导致优化器没能将std::find的通用实现完全简化为等价的范围判断,保留了通用迭代器遍历的冗余边界检查逻辑。
这种情况在GCC和clang中都存在,本质是优化器对小容器通用算法的适配规则不够全面,3这个大小刚好不在最优处理范围内——代码逻辑完全正确,只是生成的汇编不够精简。
内容的提问来源于stack exchange,提问作者justANewb stands with Ukraine
相关产品推荐
相关产品推荐

