【问题标题】:Executing performance gap between GCC and ClangGCC 和 Clang 之间的执行性能差距
【发布时间】:2019-11-08 08:09:15
【问题描述】:

我有以下简单代码,当我在 GCC 和 Clang 中编译它们时,它们的执行时间之间存在巨大的性能差异,您可以在下面看到结果和编译器的版本。

char* strchr_x(register const char* s, int c) {
  do {
    if (*s == c) {
      return (char*)s;
    }
  } while (*s++);

  return 0;
}
char* get(char* value, const char separator) {
  int separator_index = strchr(value, separator) - value;
  char* result = malloc(separator_index);
  memcpy(result, value, separator_index);
  result[separator_index] = '\0';

  return result;
}
int main() {
  const char separator = ',';
  clock_t t = clock();

  for (size_t i = 0; i < 100000000; ++i) {
    free(get("127.0.0.1, 127.0.0.2:1111", separator));
  }

  float elapsed_seconds = (((double)(clock() - t)) / CLOCKS_PER_SEC);
  printf("%f seconds.\n", elapsed_seconds);

  return 0;
}
gcc version 8.1.0 (Ubuntu 8.1.0-5ubuntu1~16.04)
# gcc main.c -O3 -o gcc-main
# 1.968750 seconds.

clang version 3.8.0-2ubuntu4 (tags/RELEASE_380/final)
# clang main.c -O3 -o clang-main
# 0.000000 seconds.

此外,“strchr_x”实现与原始 GCC 实现完全相同。你可以在https://github.com/gcc-mirror/gcc/blob/master/libiberty/strchr.c看到它

当我在标准库中使用'strchr'方法时; GCC 的运行时间减少到 0.015625 秒。

所以,我的问题是:

  1. 为什么我的 'strchr_x' 和 GCC 中标准库的 'strchr' 之间存在巨大的性能差异?
  2. 为什么 GCC 和 Clang 之间存在如此巨大的性能差距?

【问题讨论】:

  • 显然 clang 优化了 /* no effect */ for 循环。看看拆解。
  • 当时间为零时,意味着编译器完全优化了您尝试测试的代码。它可以这样做,因为代码没有任何可观察到的效果。
  • 您有未定义的行为(写到result 的末尾)。不过,这可能不是导致性能差异的原因。
  • gcc 将循环减少到for(unsigned long x=10000000;x!=0;--x);,然后无法删除这个空循环。请在 gcc 的 bugzilla 中提交错误报告。在 main 上添加 __attribute__((flatten)) 以便更早地进行内联可以让 gcc 优化,但它确实应该能够做到这一点而不会作弊。

标签: c gcc clang


【解决方案1】:

如果您将您的代码插入https://godbolt.org/,您可以看到 Clang 将优化整个 for 循环,这解释了为什么它在 0 秒内执行。它可能会这样做,因为 get 的唯一副作用是 malloc,并且结果会立即释放。

编辑: GCC 对您的代码似乎确实做得不太好...... 我再次检查了生成的汇编程序,不仅没有删除空的for循环(for strchr()),而且没有删除循环体中的函数调用(对于 strchr_x())。

所以最终这一切都归结为一些非常奇怪的优化行为,而您最慢的示例实际上是唯一一个可以做任何事情的示例。我不知道 为什么 GCC 在您的示例中失败了,但我绝对同意您应该将此作为错误提交(甚至可能是两个单独的错误,因为它已经使用 strchr() 时会失败,使用 strchr_x() 时会更失败,即使两者应该相同)

【讨论】:

  • 实际上,我想知道这是否是 GCC 方面的错误,但 @marc-glisse 已经回答了这个问题。你能回答我的第一个问题吗@felix-g?
  • 我再次检查了汇编程序,并相应地扩展了我的答案
【解决方案2】:

似乎gcc 无法删除循环,即使它删除了循环内的调用。这是带有提到的代码的godbolt链接: https://godbolt.org/z/5wamhm

main:
        push    rbx
        call    clock
        mov     rbx, rax
        mov     eax, 100000000
.L5:
        sub     rax, 1 // <<-------- loop which does nothing.
        jne     .L5
        call    clock

这绝对值得在 gcc bugzilla 中提交错误。我在这里提交错误: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92638

【讨论】:

    【解决方案3】:

    问题二的简短回答 - clang (LLVM) 在运行时具有更好的代码和链接优化。

    现在,我将首先从经验中举出一个例子。我从事的项目套装需要在 raspberry pi-3 设备上进行测试。当我使用clang++ 编译时,大约需要 15 分钟,而使用gcc 时,在等待编译超过 40 分钟后,在链接的最后,构建只是内存不足!为什么?同样,clang 提供了更好的编译时代码优化和后台链接。

    但是,这取决于,在不同的 Ox 水平下,您可能不会得到相同的差异(尝试使用 O2,您会看到)。为了更多参考,我认为这两篇文章提供了一些详细的解释。

    https://medium.com/@alitech_2017/gcc-vs-clang-llvm-an-in-depth-comparison-of-c-c-compilers-899ede2be378

    https://clang.llvm.org/comparison.html

    【讨论】:

      猜你喜欢
      • 2017-10-09
      • 1970-01-01
      • 1970-01-01
      • 2019-06-05
      • 2019-03-26
      • 2012-08-14
      • 2019-09-08
      • 2023-03-18
      • 1970-01-01
      相关资源
      最近更新 更多