【问题标题】:How do I stop GCC from optimizing this byte-for-byte copy into a memcpy call?如何阻止 GCC 将此逐字节复制优化为 memcpy 调用?
【发布时间】:2019-08-17 19:56:00
【问题描述】:

我将 memcpy 的代码作为我实现标准 C 库的一部分,该库将内存从 src 复制到 dest 一次一个字节:

void *memcpy(void *restrict dest, const void *restrict src, size_t len)
{
    char *dp = (char *restrict)dest;
    const char *sp = (const char *restrict)src;

    while( len-- )
    {
        *dp++ = *sp++;
    }

    return dest;
}

使用gcc -O2,生成的代码是合理的:

memcpy:
.LFB0:
        movq    %rdi, %rax
        testq   %rdx, %rdx
        je      .L2
        xorl    %ecx, %ecx
.L3:
        movzbl  (%rsi,%rcx), %r8d
        movb    %r8b, (%rax,%rcx)
        addq    $1, %rcx
        cmpq    %rdx, %rcx
        jne     .L3
.L2:
        ret
.LFE0:

但是,在 gcc -O3,GCC 将这个简单的逐字节复制优化为 memcpy 调用:

memcpy:
.LFB0:
        testq   %rdx, %rdx
        je      .L7
        subq    $8, %rsp
        call    memcpy
        addq    $8, %rsp
        ret
.L7:
        movq    %rdi, %rax
        ret
.LFE0:

这不起作用(memcpy 无条件调用自身),并且会导致段错误。

我尝试过传递-fno-builtin-memcpy-fno-loop-optimizations,结果还是一样。

我使用的是 GCC 版本 8.3.0:

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/local/libexec/gcc/x86_64-cros-linux-gnu/8.3.0/lto-wrapper
Target: x86_64-cros-linux-gnu
Configured with: ../configure --prefix=/usr/local --libdir=/usr/local/lib64 --build=x86_64-cros-linux-gnu --host=x86_64-cros-linux-gnu --target=x86_64-cros-linux-gnu --enable-checking=release --disable-multilib --enable-threads=posix --disable-bootstrap --disable-werror --disable-libmpx --enable-static --enable-shared --program-suffix=-8.3.0 --with-arch-64=x86-64
Thread model: posix
gcc version 8.3.0 (GCC) 

如何禁用导致副本转换为memcpy 调用的优化?

【问题讨论】:

    标签: c gcc compiler-optimization


    【解决方案1】:

    这里似乎就足够了:不要使用-fno-builtin-memcpy,而是使用-fno-builtin单独编译memcpy的翻译单元!

    另一种方法是通过-fno-tree-loop-distribute-patterns;虽然这可能很脆弱,因为它禁止编译器首先重组循环代码然后用对mem*函数的调用替换其中的一部分。

    或者,由于您不能依赖 C 库中的任何内容,因此使用 -ffreestanding 可能是合适的。

    【讨论】:

      【解决方案2】:

      这不起作用(memcpy 无条件调用自身),并且会导致段错误。

      Redefining memcpy is undefined behavior.

      如何禁用导致副本转换为 memcpy 调用的优化(最好仍然使用 -O3 进行编译)?

      不要。最好的方法是修复您的代码:

      【讨论】:

      • @JL2210 This is sub-optimal for obvious reasons 假设它编译为__builtin_memcpy 或类似的东西,那么由于显而易见的原因,您的实现更加次优。现代库使用 SIMD,甚至可能使用 DMA(如果可用),因此它们会比您的逐字节复制快得多
      • 如果这个答案至少尝试了如何解决问题会很好,因为目前它没有提供任何有用的信息(除了告诉我修复我的代码或重命名函数)。是否有可能提出一个解决方案(除了“不要这样做”)来帮助我摆脱未定义的行为领域?
      • @JL2210 如果我现在理解了你的问题,那么你向我们展示的函数不应该被称为“memcpy”,因为你感兴趣的是防止普通的循环副本被重写由编译器调用 memcpy。但是由于您将其命名为“memcpy”,因此您的秘密意图用您自己的替换 memcpy 有点清楚,这带来了 Acorn 开始解决的新问题。
      • @JL2210 对于您没有自己的优化代码(即 asm 代码)的架构,不理会原始内置 memcpy 可能是有意义的。另外,除非您明确说明,否则我认为编译器不会调用您自己的版本。你如何处理 ODR,它甚至适用于编译器内置函数吗?
      • @AndrewHenle 好吧,如果 OP 合法地实现了memcpy(),他们应该告诉编译器禁用整个标准 C 库...
      猜你喜欢
      • 1970-01-01
      • 2021-11-23
      • 1970-01-01
      • 1970-01-01
      • 2012-12-14
      • 1970-01-01
      • 2016-01-25
      • 1970-01-01
      • 2010-09-11
      相关资源
      最近更新 更多