在我做其他任何事情之前,我想我先在这里分享一下这个意见。我在设计算法时发现,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;
}
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
info:
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)
因此我们获得了大约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
info:
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
超过 2.5倍 !!有什么想法吗?
我将__builtin_memcpy
函数替换为memcpy
以测试出来,这次编译后的代码在两侧运行大约34秒 - 按预期一致且较慢。
两个编译器似乎对__builtin_memcpy
和bitmasking的组合有很大不同的解释。
我看了一下汇编代码,但看不出任何可以解释性能下降的东西,因为我不是asm专家。
编辑03-05-2018: 发布此错误:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84719。
答案 0 :(得分:3)
我发现你为memcpy和__builtin_memcpy获得了不同的代码。我不认为这应该发生,事实上我无法在我的(linux)系统上重现它。
如果在for循环之前添加#pragma GCC unroll 16
(在gcc-8 +中实现),gcc将获得与clang相同的perf(使block
常量对于优化代码至关重要),所以基本上llvm的展开比gcc更具侵略性,取决于案例,这可能是好的还是坏的。不过,请随时向gcc报告,也许他们会在某一天调整展开的启发式方法,并且额外的测试用例可以提供帮助。
一旦完成展开,gcc对某些值(block
特别等于4或8)没有问题,但对于其他一些值更差,特别是3.但是如果没有更小的测试用例,则可以更好地分析块上的循环。 Gcc似乎与memcpy(,,3)
有问题,如果你总是读取8个字节(下一行已经处理额外的字节IIUC),它会更好。另一件事可以报告给gcc。