gpn*_*uma 6 c performance gcc clang
在我做其他任何事情之前,我想我先在这里分享一下这个意见.我在设计算法时发现gcc编译的代码性能对于一些简单的代码与clang相比是灾难性的.
如何重现
创建test.c包含此代码的文件:
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
int main(int argc, char *argv[]) {
const uint64_t size = 1000000000;
const size_t alloc_mem = size * sizeof(uint8_t);
uint8_t *mem = (uint8_t*)malloc(alloc_mem);
for (uint_fast64_t i = 0; i < size; i++)
mem[i] = (uint8_t) (i >> 7);
uint8_t block = 0;
uint_fast64_t counter = 0;
uint64_t total = 0x123456789abcdefllu;
uint64_t receiver = 0;
for(block = 1; block <= 8; block ++) {
printf("%u ...\n", block);
counter = 0;
while (counter < size - 8) {
__builtin_memcpy(&receiver, &mem[counter], block);
receiver &= (0xffffffffffffffffllu >> (64 - ((block) << 3)));
total += ((receiver * 0x321654987cbafedllu) >> 48);
counter += block;
}
}
printf("=> %llu\n", total);
return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)
GCC
编译并运行:
gcc-7 -O3 test.c
time ./a.out
1 ...
2 ...
3 ...
4 ...
5 ...
6 ...
7 ...
8 ...
=> 82075168519762377
real 0m23.367s
user 0m22.634s
sys 0m0.495s
Run Code Online (Sandbox Code Playgroud)
信息:
gcc-7 -v
Using built-in specs.
COLLECT_GCC=gcc-7
COLLECT_LTO_WRAPPER=/usr/local/Cellar/gcc/7.3.0/libexec/gcc/x86_64-apple-darwin17.4.0/7.3.0/lto-wrapper
Target: x86_64-apple-darwin17.4.0
Configured with: ../configure --build=x86_64-apple-darwin17.4.0 --prefix=/usr/local/Cellar/gcc/7.3.0 --libdir=/usr/local/Cellar/gcc/7.3.0/lib/gcc/7 --enable-languages=c,c++,objc,obj-c++,fortran --program-suffix=-7 --with-gmp=/usr/local/opt/gmp --with-mpfr=/usr/local/opt/mpfr --with-mpc=/usr/local/opt/libmpc --with-isl=/usr/local/opt/isl --with-system-zlib --enable-checking=release --with-pkgversion='Homebrew GCC 7.3.0' --with-bugurl=https://github.com/Homebrew/homebrew-core/issues --disable-nls
Thread model: posix
gcc version 7.3.0 (Homebrew GCC 7.3.0)
Run Code Online (Sandbox Code Playgroud)
所以我们得到大约23秒的用户时间.现在让我们对cc(macOS上的clang)做同样的事情:
铛
cc -O3 test.c
time ./a.out
1 ...
2 ...
3 ...
4 ...
5 ...
6 ...
7 ...
8 ...
=> 82075168519762377
real 0m9.832s
user 0m9.310s
sys 0m0.442s
Run Code Online (Sandbox Code Playgroud)
信息:
Apple LLVM version 9.0.0 (clang-900.0.39.2)
Target: x86_64-apple-darwin17.4.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
Run Code Online (Sandbox Code Playgroud)
这比快2.5倍!有什么想法吗 ?
我替换了__builtin_memcpy函数memcpy来测试出来的东西,这次编译的代码在两边都运行大约34秒 - 按预期一致和慢.
看起来__builtin_memcpy两个编译器对和掩码的组合的解释非常不同.我看了一下汇编代码,但看不出任何可以解释性能下降的东西,因为我不是asm专家.
编辑03-05-2018: 发布此错误:https://gcc.gnu.org/bugzilla/show_bug.cgi?id = 84719.
我发现您为 memcpy 与 __builtin_memcpy 获得了不同的代码,这很可疑。我认为这不应该发生,而且实际上我无法在我的(linux)系统上重现它。
#pragma GCC unroll 16如果在 for 循环之前添加(在 gcc-8+ 中实现),gcc 会获得与 clang 相同的性能(制作block常量对于优化代码至关重要),因此本质上 llvm 的展开比 gcc 的展开更积极,这可能是好的,也可能是不好视情况而定。不过,请随时向 gcc 报告,也许有一天他们会调整展开启发式,并且额外的测试用例可能会有所帮助。
一旦处理完展开,gcc 对于某些值(block特别是等于 4 或 8)来说效果很好,但对于其他一些值(特别是 3)来说则要差得多。但是使用块上没有循环的较小测试用例进行分析会更好。Gcc 似乎在使用 时遇到了麻烦memcpy(,,3),如果你总是读取 8 个字节,它会工作得更好(下一行已经处理了额外的字节 IIUC)。另一件可以向海湾合作委员会报告的事情。