与clang相比,gcc的__builtin_memcpy性能与一定数量的字节相比是可怕的

时间:2018-03-04 17:51:36

标签: 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;
}

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

1 个答案:

答案 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。