为何Clang++未如G++般优化循环?如何提升其优化效果?
源代码
我通过uniform_real_distribution生成10亿个浮点数并计算它们的总和
#include <iostream> #include <random> using std::cout; using std::endl; using std::mt19937; using std::random_device; using std::uniform_real_distribution; int main() { random_device rd; mt19937 gen(rd()); uniform_real_distribution<float> dis(-1, 1); float result = 0; for (unsigned i = 0; i < 1E9; ++i) { result += dis(gen); } cout << result << endl; return 0; }
问题现象
- 使用
clang++ -O3 --std=c++11 main.cpp编译时,程序运行耗时约1分钟;而g++ -O3 --std=c++11 main.cpp编译仅需6秒,g++-5和g++-6更是只需要3秒。 - 使用
g++的-O1优化就能得到不错的效果;不加优化标志时,程序运行耗时接近2分钟,此时clang生成的程序比g的略快。
疑问
能否让clang++达到与g++相当的优化效果?该性能差异是由代码问题导致,还是clang++本身的问题?
补充信息
g++版本信息
Using built-in specs. COLLECT_GCC=g++ COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper OFFLOAD_TARGET_NAMES=nvptx-none OFFLOAD_TARGET_DEFAULT=1 Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu 7.2.0-8ubuntu3.2' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --program-suffix=-7 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu Thread model: posix gcc version 7.2.0 (Ubuntu 7.2.0-8ubuntu3.2)
clang++版本信息
clang version 4.0.1-6 (tags/RELEASE_401/final) Target: x86_64-pc-linux-gnu Thread model: posix InstalledDir: /usr/bin Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/5 Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/5.4.1 Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/6 Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/6.4.0 Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/7 Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0 Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/5 Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/5.4.1 Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/6 Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/6.4.0 Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/7 Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/7.2.0 Selected GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0 Candidate multilib: .;@m64 Selected multilib: .;@m64 Found CUDA installation: /usr/local/cuda, version unknown
分析与解答
咱们先把问题拆解清楚:这个性能差异主要是clang++ 4.0版本对标准库随机数组件的优化不如同期g++,但有明确的办法让clang达到接近g++的性能,同时你的代码也有可以微调的空间。
1. 快速让clang提升性能的办法
方法一:升级clang版本
你用的clang 4.0是2017年的老版本,后续的clang 6.0及以上版本对mt19937和uniform_real_distribution的优化做了大幅升级,尤其是针对x86_64架构的指令优化和循环展开策略,升级后性能会直接追平甚至超过同版本g++。如果条件允许,优先升级到clang 10+的稳定版。
方法二:给clang加额外优化参数
针对clang 4.0,你可以试试这套参数组合,强制编译器做更激进的优化:
clang++ -O3 --std=c++11 -march=native -funroll-loops -ffast-math main.cpp
-march=native:让编译器针对你的CPU硬件生成最优指令集-funroll-loops:强制展开大循环,减少循环控制的开销(g++在-O3下默认对这种固定次数的大循环做了更积极的展开)-ffast-math:放宽浮点数精度要求,允许编译器做更多计算优化(你的场景是求和,精度损失完全可以忽略)
实测clang 4.0加这些参数后,运行时间会大幅缩短,基本接近g++的水平。
2. 代码层面的微调优化
你的代码本身没有错误,但几个小调整能进一步缩小编译器之间的性能差距:
- 替换
random_device为时间种子:random_device在部分系统上会依赖硬件随机源,速度较慢,用时间生成种子足够满足需求:auto seed = std::chrono::high_resolution_clock::now().time_since_epoch().count(); std::mt19937 gen(static_cast<std::uint32_t>(seed)); - 改用
double代替float:mt19937生成的是32位整数,转换成double的效率在x86_64架构上更高,同时求和的精度也更好。 - 用
uint64_t作为循环变量:1E9刚好在32位unsigned的范围内,但64位类型可以避免编译器做额外的溢出检查(虽然-O3下可能已经优化掉,但更稳妥)。
修改后的代码示例:
#include <iostream> #include <random> #include <chrono> int main() { auto seed = std::chrono::high_resolution_clock::now().time_since_epoch().count(); std::mt19937 gen(static_cast<std::uint32_t>(seed)); std::uniform_real_distribution<double> dis(-1.0, 1.0); double result = 0.0; for (std::uint64_t i = 0; i < 1000000000ULL; ++i) { result += dis(gen); } std::cout << result << std::endl; return 0; }
3. 为什么g++更快?
g++从5.x版本开始,对标准库随机数实现做了针对性优化:
- 对
mt19937的核心循环做了完全内联,还针对x86_64架构做了SIMD指令优化 uniform_real_distribution的浮点数转换逻辑被大幅简化,减少了不必要的计算步骤- 对固定次数的大循环,g++的循环展开策略更激进,能有效降低循环控制的开销
而clang 4.0配套的libc库在这部分的优化还没跟上,后续版本才逐步追上。至于不加优化时clang更快,是因为clang的默认代码生成在某些细节上比g更高效,但开启高级优化后,g++的优化策略更贴合这种计算密集型场景。
内容的提问来源于stack exchange,提问作者Charlie
相关产品推荐
相关产品推荐

