C++ 为什么这个无操作循环没有优化掉?

C++ 为什么这个无操作循环没有优化掉?,c++,gcc,optimization,C++,Gcc,Optimization,下面的代码从一个解释为浮点的零数组复制到另一个数组,并打印此操作的时间。正如我看到的许多情况一样,编译器(包括gcc)只是优化了没有op循环的情况,我一直在等待,在更改复制数组程序时,它会停止复制 #include <iostream> #include <cstring> #include <sys/time.h> static inline long double currentTime() { timespec ts; clock_ge

下面的代码从一个解释为浮点的零数组复制到另一个数组,并打印此操作的时间。正如我看到的许多情况一样,编译器(包括gcc)只是优化了没有op循环的情况,我一直在等待,在更改复制数组程序时,它会停止复制

#include <iostream>
#include <cstring>
#include <sys/time.h>

static inline long double currentTime()
{
    timespec ts;
    clock_gettime(CLOCK_MONOTONIC,&ts);
    return ts.tv_sec+(long double)(ts.tv_nsec)*1e-9;
}

int main()
{
    size_t W=20000,H=10000;

    float* data1=new float[W*H];
    float* data2=new float[W*H];
    memset(data1,0,W*H*sizeof(float));
    memset(data2,0,W*H*sizeof(float));

    long double time1=currentTime();
    for(int q=0;q<16;++q) // take more time
        for(int k=0;k<W*H;++k)
            data2[k]=data1[k];
    long double time2=currentTime();

    std::cout << (time2-time1)*1e+3 << " ms\n";

    delete[] data1;
    delete[] data2;
}
#包括
#包括
#包括
静态内联长双currentTime()
{
timespects;
时钟获取时间(时钟单调,&ts);
返回ts.tv\U sec+(长双精度)(ts.tv\U nsec)*1e-9;
}
int main()
{
尺寸W=20000,H=10000;
浮动*数据1=新浮动[W*H];
浮动*数据2=新浮动[W*H];
memset(数据1,0,W*H*sizeof(float));
memset(数据2,0,W*H*sizeof(float));
长双倍时间1=当前时间();

对于(int q=0;q为什么您希望编译器对此进行优化?通常很难证明写入任意内存地址是“不可操作的”。在您的情况下,这是可能的,但需要编译器通过
new
跟踪堆内存地址(由于这些地址是在运行时生成的,所以这又一次很困难),而且这样做确实没有动机

毕竟,你明确地告诉编译器你想分配内存并对其进行写入。可怜的编译器怎么知道你一直在骗它


特别是,问题是堆内存可能会被其他很多东西所混淆。它恰好是进程的私有内存,但正如我上面所说的,证明这对于编译器来说是一项大量的工作,而不是函数本地内存。

编译器能够知道这是一个不可操作的唯一方法是,如果它知道
memset
does。为了实现这一点,函数必须在头中定义(通常不是),或者编译器必须将其视为一个特殊的内在函数。但是,如果不使用这些技巧,编译器只会看到对未知函数的调用,这可能会产生副作用,并对两个调用中的每个调用执行不同的操作。

无论如何,这不是不可能的(clang++3.3版):

程序为我打印0.000367毫秒…并查看汇编语言:

...
callq   clock_gettime
movq    56(%rsp), %r14
movq    64(%rsp), %rbx
leaq    56(%rsp), %rsi
movl    $1, %edi
callq   clock_gettime
...
而对于g++:

...
call    clock_gettime
fildq   32(%rsp)
movl    $16, %eax
fildq   40(%rsp)
fmull   .LC0(%rip)
faddp   %st, %st(1)
.p2align 4,,10
.p2align 3
.L2:
 movl    $1, %ecx
 xorl    %edx, %edx
 jmp     .L5
 .p2align 4,,10
 .p2align 3
 .L3:
 movq    %rcx, %rdx
 movq    %rsi, %rcx
 .L5:
 leaq    1(%rcx), %rsi
 movss   0(%rbp,%rdx,4), %xmm0
 movss   %xmm0, (%rbx,%rdx,4)
 cmpq    $200000001, %rsi
 jne     .L3
 subl    $1, %eax
 jne     .L2
 fstpt   16(%rsp)
 leaq    32(%rsp), %rsi
 movl    $1, %edi
 call    clock_gettime
 ...
编辑(g++v4.8.2/clang++v3.3)

源代码-原始版本(1)

源代码-修改版本(2)


现在,未优化的情况是(1)+g++

这个问题中的代码发生了很大的变化,使正确的答案无效。这个答案适用于第五个版本:由于代码当前试图读取未初始化的内存,优化器可以合理地假设发生了意外的事情

许多优化步骤都有类似的模式:有一种与当前编译状态匹配的指令模式。如果该模式在某个点匹配,则匹配的模式是(参数化的)替换为更高效的版本。这种模式的一个非常简单的示例是定义一个随后未使用的变量;在这种情况下,替换只是删除


这些模式是为正确的代码而设计的。在错误的代码中,模式可能根本无法匹配,或者它们可能以完全意外的方式匹配。第一种情况不会导致优化,第二种情况可能导致完全不可预测的结果(如果进一步优化修改后的代码,则肯定会导致不可预测的结果)

从我们的角度来看,这不是op,但编译器不知道也不知道来自data1和data2的内存块的内容已经相等。我不能给出明确的答案,但a)任何优化都不是强制性的。b)你有两个
new
,但没有
delete
@deviantfan我依赖于退出时释放的内存,尽管这似乎没有得到标准的保证。即使主要操作系统可以做到这一点,忽略delete显然是错误的。据我所知,这甚至可以解决你的优化问题(“可能”,而不是“将”)@pan-:但它确实知道没有使用data2计算的结果。我同意OP的说法,这种代码在我的实践中通常是经过优化的,我必须采取特殊的步骤来确保在编写这样的基准测试时不会发生这种情况。没有进一步的内存引用,就是这样。我希望编译器检查数据依赖关系而且只对
易失性
内存说“我不知道”。因此,期待一些足够积极的优化似乎是很自然的。@VioletGiraffe证明了这一点。提示:这真的很难。在这种情况下,它真的很简单。@KonradRudolph无论如何,你对新内存的担忧似乎不是(完全)的有效。我已经更新了问题,删除了
memset
new
操作符。我想说问题主要不是关于
memset
,而是关于
data2[k]=data1[k]
循环(因为
data2
之后不再被访问)。注释
memset
调用不会改变行为(尽管由于某些原因6950毫秒变为5500毫秒)。同样的论点也适用于
运算符new
。实际上,没有任何动机让内存分配成为编译器的内在特性,因此编译器可能不知道操作系统内存分配过程的所有复杂性。@KonradRudolph:实际上,除了使用内在特性之外,编译器还会作弊。例如,在LLVM、
malloc
free
(以及
new
delete
的损坏名称)是优化器已知的,因此如果优化器意识到内存未使用(例如,在优化之后)然后它还优化了分配/解除分配。这当然违反了任何依赖于副作用的实现(并且使用
LD\u LIBRARY\u PRELOAD
他们无法知道),但这是一种使用的策略。@MatthieuM。他们当然会。我的意思是,你不能指望他们作弊。我有点惊讶LLVM会费心去连接
malloc

...
call    clock_gettime
fildq   32(%rsp)
movl    $16, %eax
fildq   40(%rsp)
fmull   .LC0(%rip)
faddp   %st, %st(1)
.p2align 4,,10
.p2align 3
.L2:
 movl    $1, %ecx
 xorl    %edx, %edx
 jmp     .L5
 .p2align 4,,10
 .p2align 3
 .L3:
 movq    %rcx, %rdx
 movq    %rsi, %rcx
 .L5:
 leaq    1(%rcx), %rsi
 movss   0(%rbp,%rdx,4), %xmm0
 movss   %xmm0, (%rbx,%rdx,4)
 cmpq    $200000001, %rsi
 jne     .L3
 subl    $1, %eax
 jne     .L2
 fstpt   16(%rsp)
 leaq    32(%rsp), %rsi
 movl    $1, %edi
 call    clock_gettime
 ...
...
size_t W=20000,H=10000;

float* data1=new float[W*H];
float* data2=new float[W*H];
...
...
const size_t W=20000;
const size_t H=10000;

float data1[W*H];
float data2[W*H];
...