Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/151.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 为什么不是';t G++;编译器是否以相同的方式处理这两个函数?_C++_Performance - Fatal编程技术网

C++ 为什么不是';t G++;编译器是否以相同的方式处理这两个函数?

C++ 为什么不是';t G++;编译器是否以相同的方式处理这两个函数?,c++,performance,C++,Performance,我有一个带有0和1的数组A。我想找到A中所有数字的总和。我想测试两个函数: 第一个功能 void test1(int curIndex){ if(curIndex == size) return; test1(curIndex+1); s+=A[curIndex]; } void test2(int curIndex){ if(curIndex == size) return; s+=A[curIndex]; test2(curIndex+1);

我有一个带有0和1的数组
A
。我想找到
A
中所有数字的总和。我想测试两个函数:

第一个功能

void test1(int curIndex){
    if(curIndex == size) return;
    test1(curIndex+1);
    s+=A[curIndex];
}
void test2(int curIndex){
    if(curIndex == size) return;
    s+=A[curIndex];
    test2(curIndex+1);
}
第二功能

void test1(int curIndex){
    if(curIndex == size) return;
    test1(curIndex+1);
    s+=A[curIndex];
}
void test2(int curIndex){
    if(curIndex == size) return;
    s+=A[curIndex];
    test2(curIndex+1);
}
我用数字来计算指令的数量,下面是整个实验:

#include <iostream>
#include <fstream>
#include "Statistics.h"

using namespace std;


int size;
int *A;
int s;

void test3(int curIndex){
    if(curIndex == size) return;
    test3(curIndex+1);
    s+=A[curIndex];
}

int main(int argc, char* argv[]){

    size = atoi(argv[1]);
    if(argc!=2){
        cout<<"type ./executable size{odd integer}"<<endl;
        return 1;
    }
    if(size%2!=1){
        cout<<"size must be an odd number"<<endl;
        return 1;
    }
    A = new int[size];
    int i;
    for(i=0;i<size;i++){
        if(i%2==0){
            A[i] = false;
        }
        else{
            A[i] = true;
        }
    }

    Statistics stat(1);
    stat.start();
    test3(0);
    stat.stop();
    stat.printWithHelp();
    cout<<s<<endl;

    return 0;
}
这是当
A
的大小为
111111

L2 accesses: 24126
L2 miss/access ratio: 0.131559
L3 accesses: 3174
L3 misses: 587
L3 miss/access ratio: 0.18494
Instructions: 1022776
Branches: 178113
Branch mispredictions: 6976
Branch miss/predict ratio: 0.0391661
L2 accesses: 7090
L2 miss/access ratio: 0.163752
L3 accesses: 1161
L3 misses: 507
L3 miss/access ratio: 0.436693
Instructions: 555860
Branches: 111189
Branch mispredictions: 25
Branch miss/predict ratio: 0.000224842
这是当
A
的大小为
111111

L2 accesses: 24126
L2 miss/access ratio: 0.131559
L3 accesses: 3174
L3 misses: 587
L3 miss/access ratio: 0.18494
Instructions: 1022776
Branches: 178113
Branch mispredictions: 6976
Branch miss/predict ratio: 0.0391661
L2 accesses: 7090
L2 miss/access ratio: 0.163752
L3 accesses: 1161
L3 misses: 507
L3 miss/access ratio: 0.436693
Instructions: 555860
Branches: 111189
Branch mispredictions: 25
Branch miss/predict ratio: 0.000224842

为什么结果会有差异?指令减少一半,分支预测失误几乎被消除。这里发生了什么?

您的第二个函数是尾部递归函数。这意味着编译器可以将其优化为:

void test2(int curIndex){
  while(true)
  {
    if(curIndex == size) return;
    s+=A[curIndex];
    curIndex = curIndex + 1;
  }
}
这大大减少了指令的数量。它还将所需的堆栈帧数减少到(最多)一个。因此,它使用了更少的内存,从而减少了缓存未命中

编译器无法对第一个函数进行此优化

更新: 有些人问为什么编译器不能对第一个函数进行优化

让我们从观察函数不是尾部递归开始。如果发生的最后一件事是对同一函数的递归调用,然后返回该递归调用的结果(如果有的话),则函数是尾部递归的

显然,对于第一个函数,
s+=A[curIndex]在递归调用后执行

于是人们问为什么编译器不能将第一个函数转换成第二个函数

这应该是它的结束,但人们当然会想知道为什么没有人设计、实现和测试这个特性。嗯,也许没人想过要这么做。但更重要的是,该功能绝不是微不足道的

首先,编译器必须理解这一点

test1(curIndex+1);
s+=A[curIndex];

它们是等价的。这是一个不平凡的观察,因为从机械的角度来看,它们是不等价的!事实上,第一个循环实际上是从数组的末尾到开始循环,而第二个循环是从开始到结束循环。是一样的吗?当A是int*时,它会产生相同的结果(而s是int中的),但在其他情况下(例如,当A是double*而s是double时),它不会产生相同的结果。我们期望编译器有那么聪明吗


因此,这里我们有一个潜在的功能,实现成本很高。但如果收益高,成本可能是值得的。效益高吗?我猜这在实际代码中很少发生,也就是说,开发人员无论如何都可能编写第二种形式。因此,你有它:一个昂贵的功能,几乎没有好处。依我看,编译器开发人员明智地将宝贵的时间花在更有用的功能上。

@PSIAlt,第二个也是递归的,尽管它符合尾部调用优化的条件(我认为)。谷歌“尾部递归”。由于递归调用在最后,编译器可以跳过堆栈,而不必跟踪调用下一步的每个实例。(认识到某些代码可以简化为特殊模式要比认识到它已经在特殊模式中困难得多。)“为什么G++编译器不够聪明,不能意识到这两个函数完全相同?”因为它们不是……这些函数执行加法的顺序不同。推断它们仍然产生相同的结果是相当困难的。(是一个说明差异的字符串版本。)@molbdnilo:就此而言,如果不使用
-fwrapv
,整数加法不一定是关联的。将
INT_MAX
1
-1
按一个顺序相加会导致行为未定义的溢出,而按另一个顺序则会产生已定义的结果。因此,在某些情况下(可能不是这一种),gcc需要谨慎,以避免违反其在其他地方的优化中所做的假设,将好代码变成坏代码。但最大的问题是:为什么编译器不能优化第一个函数?或者:是否有一个编译器可以进行这种优化?优化是否符合C++标准?@ KNIVIL——优化问题总是“一致的程序能区分吗?”在一致程序中缓存命中或遗漏是不可检测的。@ YAKK语言如C++,证明了任何操作都可以用非可链接函数调用来交换,这是非常困难的。到了许多编译器都不费心的地步。我知道GCC至少在某些情况下可以做到这一点,因为它已被明确告知(使用
\uuuuu属性\uuuu
)的函数没有副作用,但这不是这样的函数。@zwol完全正确!这个问题并不是一个关于gcc在代码推理方面有多差的例子,而是一个关于一个问题有多简单的例子,对于一个程序员来说,这个问题看起来仍然非常像停顿的问题compiler@knivil请注意,我只讨论尾部递归函数的优化,这是尾部调用优化的一个特例。在递归情况下,只涉及一个函数,很容易在源代码中演示优化。一般TCO将调用替换为跳转,可能跳转到不同函数的主体中,并且不能始终在源代码中演示。根据调用约定,在程序集级别仍然相对容易。