Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/150.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

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/algorithm/10.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

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/vim/5.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++ 为什么DFS在一棵树中较慢,而在另一棵树中较快?_C++_Algorithm_Performance_Caching_Tree - Fatal编程技术网

C++ 为什么DFS在一棵树中较慢,而在另一棵树中较快?

C++ 为什么DFS在一棵树中较慢,而在另一棵树中较快?,c++,algorithm,performance,caching,tree,C++,Algorithm,Performance,Caching,Tree,更新:原来生成树的解析器中有一个bug。更多信息请参见最终编辑。 设T为二叉树,使每个内部节点正好有两个子节点。对于这棵树,我们要编写一个函数,用于为T中的每个节点v查找由v定义的子树中的节点数 示例 输入 期望输出 红色表示我们要计算的数字。树的节点将存储在一个数组中,让我们按照预排序布局将其称为treearlay 对于上面的示例,trearray将包含以下对象: 10,11,0,12,13,2,7,3,14,1,15,16,4,8,17,18,5,9,6 树的节点由以下结构描述: str

更新:原来生成树的解析器中有一个bug。更多信息请参见最终编辑。

T
为二叉树,使每个内部节点正好有两个子节点。对于这棵树,我们要编写一个函数,用于为
T
中的每个节点
v
查找由
v
定义的子树中的节点数

示例

输入

期望输出

红色表示我们要计算的数字。树的节点将存储在一个数组中,让我们按照预排序布局将其称为
treearlay

对于上面的示例,
trearray
将包含以下对象:

10,11,0,12,13,2,7,3,14,1,15,16,4,8,17,18,5,9,6

树的节点由以下结构描述:

struct tree_node{

    long long int id; //id of the node, randomly generated
    int numChildren; //number of children, it is 2 but for the leafs it's 0
    int size; //size of the subtree rooted at the current node,
    // what we want to compute

    int pos; //position in TreeArray where the node is stored
    int lpos; //position of the left child
    int rpos; //position of the right child

    tree_node(){
        id = -1;
        size = 1;
        pos = lpos = rpos = -1;
        numChildren = 0;
    }

};
用于计算所有
大小
值的函数如下所示:

void testCache(int cur){

    if(treeArray[cur].numChildren == 0){
        treeArray[cur].size = 1;
        return;
    }

    testCache(treeArray[cur].lpos);
    testCache(treeArray[cur].rpos);

    treeArray[cur].size = treeArray[treeArray[cur].lpos].size + 
    treeArray[treeArray[cur].rpos].size + 1;

}
我想了解为什么当
T
看起来像这样(几乎像一条左行链)时,此函数会更快

T
看起来像这样时(几乎像一条右转的链条)会变慢:

以下实验在Intel(R)Core(TM)i5-3470 CPU上运行,运行频率为3.20GHz,内存为8GB,一级缓存256KB,二级缓存1MB,三级缓存6MB

图中的每个点都是以下for循环的结果(参数由轴定义):

通过键入
g++-O3-std=c++11 file.cpp编译
通过键入
/executable tree.txt
运行。在
tree.txt
中,我们将树存储在

是一棵左行的树,有10^5片叶子

是一棵有10^5片叶子的树

我得到的跑步时间: 左行树木约0.07秒 约0.12秒用于正确行驶的树木

很抱歉我写了这么长的帖子,但是考虑到问题的范围似乎很窄,我找不到更好的方式来描述它

提前谢谢你

编辑:

这是史密斯先生回答后的后续编辑。我知道地方扮演着非常重要的角色,但我不确定我是否理解这里的情况

对于上面的两个示例树,让我们看看如何随时间访问内存

对于左边的树:

对于正确的路径树:

在我看来,在这两种情况下,我们都有本地访问模式

编辑:

以下是关于条件分支数量的曲线图:

以下是有关分支预测失误数量的图表:

是一棵左行的树,有10^6片叶子

是一棵有10^6片叶子的树

最终编辑:

我想为浪费大家的时间而道歉,我使用的解析器有一个参数,表示我希望使我的树看起来像“左”或“右”。这是一个浮点数,必须接近0才能向左移动,接近1才能向右移动。然而,要使它看起来像一条链,它必须非常小,比如
0.000000001
0.99999999
。对于较小的输入,即使对于
0.0001
这样的值,树看起来也像一条链。我认为这个数字足够小,它也会为较大的树提供一个链,然而,正如我将要展示的那样,情况并非如此。如果使用像
0.00000000 1
这样的数字,解析器将由于浮点问题而停止工作

瓦迪克罗博特的回答表明我们有地方问题。受他的实验启发,我决定对上面的访问模式图进行概括,以了解它不仅在示例树中,而且在任何树中的行为

我修改了vadikrobot的代码如下:

void testCache(int cur, FILE *f) {

    if(treeArray[cur].numChildren == 0){
        fprintf(f, "%d\t", tim++);
        fprintf (f, "%d\n", cur);
        treeArray[cur].size = 1;
        return;
    }

    fprintf(f, "%d\t", tim++);
    fprintf (f, "%d\n", cur);
    testCache(treeArray[cur].lpos, f);
    fprintf(f, "%d\t", tim++);
    fprintf (f, "%d\n", cur);
    testCache(treeArray[cur].rpos, f);
    fprintf(f, "%d\t", tim++);
    fprintf (f, "%d\n", cur);
    fprintf(f, "%d\t", tim++);
    fprintf (f, "%d\n", treeArray[cur].lpos);
    fprintf(f, "%d\t", tim++);
    fprintf (f, "%d\n", treeArray[cur].rpos);
    treeArray[cur].size = treeArray[treeArray[cur].lpos].size + 
    treeArray[treeArray[cur].rpos].size + 1;
}
由错误的解析器生成的访问模式

让我们看看左边一棵有10片叶子的树

看起来很不错,正如上面的图表中所预测的(我只是在上面的图表中忘记了这样一个事实:当我们找到一个节点的大小时,我们还访问该节点的大小参数,
cur
,在上面的源代码中)

让我们看看左边一棵有100片叶子的树

看起来和预期的一样。1000片树叶怎么样

这绝对不是意料之中的。右上角有一个小三角形。这是因为这棵树看起来不像是一条左行的链条,在最后的某个地方有一个小的子树。当叶数为10^4时,问题变得更大

让我们看看正确的树会发生什么。当叶数为10时:

看起来不错,100片叶子怎么样

看起来也不错。这就是为什么我质疑正确树木的位置,对我来说,它们至少在理论上都是局部的。现在,如果您尝试增大尺寸,会发生一些有趣的事情:

对于1000页:

对于10^4页,事情变得更加混乱:

由正确的解析器生成的访问模式

我没有使用通用解析器,而是为这个特定问题创建了一个解析器:

#include <iostream>
#include <fstream>

using namespace std;

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

    if(argc!=4){
        cout<<"type ./executable n{number of leafs} type{l:left going, r:right going} outputFile"<<endl;
        return 0;
    }

    int i;

    int n = atoi(argv[1]);

    if(n <= 2){cout<<"leafs must be at least 3"<<endl; return 0;}

    char c = argv[2][0];

    ofstream fout;
    fout.open(argv[3], ios_base::out);

    if(c == 'r'){

        for(i=0;i<n-1;i++){

            fout<<"("<<i<<",";

        }
        fout<<i;
        for(i=0;i<n;i++){
            fout<<")";
        }
        fout<<";"<<endl;

    }
    else{

        for(i=0;i<n-1;i++){
            fout<<"(";
        }

        fout<<1<<","<<n<<")";

        for(i=n-1;i>1;i--){
            fout<<","<<i<<")";
        }
        fout<<";"<<endl;

    }

    fout.close();


return 0;
}
#包括
#包括
使用名称空间std;
int main(int argc,char*argv[]){
如果(argc!=4){

cout由于节点在内存中的位置不同,缓存未命中的情况也不同。如果您按照节点在内存中的顺序访问节点,则很可能缓存已经从缓存中的ram中加载了它们(因为加载缓存页(很可能比您的一个节点大)

如果您以随机顺序(相对于RAM中的位置)或相反顺序访问节点,则更可能是缓存尚未从RAM加载节点

因此,区别不是因为树的结构,而是树节点在RAM中的位置与您想要访问它们的顺序相比


编辑:(将访问模式添加到问题后):

正如您在访问模式gra上看到的那样
void testCache(int cur, FILE *f) {

    if(treeArray[cur].numChildren == 0){
        fprintf(f, "%d\t", tim++);
        fprintf (f, "%d\n", cur);
        treeArray[cur].size = 1;
        return;
    }

    fprintf(f, "%d\t", tim++);
    fprintf (f, "%d\n", cur);
    testCache(treeArray[cur].lpos, f);
    fprintf(f, "%d\t", tim++);
    fprintf (f, "%d\n", cur);
    testCache(treeArray[cur].rpos, f);
    fprintf(f, "%d\t", tim++);
    fprintf (f, "%d\n", cur);
    fprintf(f, "%d\t", tim++);
    fprintf (f, "%d\n", treeArray[cur].lpos);
    fprintf(f, "%d\t", tim++);
    fprintf (f, "%d\n", treeArray[cur].rpos);
    treeArray[cur].size = treeArray[treeArray[cur].lpos].size + 
    treeArray[treeArray[cur].rpos].size + 1;
}
#include <iostream>
#include <fstream>

using namespace std;

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

    if(argc!=4){
        cout<<"type ./executable n{number of leafs} type{l:left going, r:right going} outputFile"<<endl;
        return 0;
    }

    int i;

    int n = atoi(argv[1]);

    if(n <= 2){cout<<"leafs must be at least 3"<<endl; return 0;}

    char c = argv[2][0];

    ofstream fout;
    fout.open(argv[3], ios_base::out);

    if(c == 'r'){

        for(i=0;i<n-1;i++){

            fout<<"("<<i<<",";

        }
        fout<<i;
        for(i=0;i<n;i++){
            fout<<")";
        }
        fout<<";"<<endl;

    }
    else{

        for(i=0;i<n-1;i++){
            fout<<"(";
        }

        fout<<1<<","<<n<<")";

        for(i=n-1;i>1;i--){
            fout<<","<<i<<")";
        }
        fout<<";"<<endl;

    }

    fout.close();


return 0;
}
void testCache(int cur, FILE *f) {
   if(treeArray[cur].numChildren == 0){
       fprintf (f, "%d\n", cur);
       treeArray[cur].size = 1;
       return;
   }

   fprintf (f, "%d\n", cur);
   testCache(treeArray[cur].lpos, f);
   fprintf (f, "%d\n", cur);
   testCache(treeArray[cur].rpos, f);

   fprintf (f, "%d\n", treeArray[cur].lpos);
   fprintf (f, "%d\n", treeArray[cur].rpos);
   treeArray[cur].size = treeArray[treeArray[cur].lpos].size + treeArray[treeArray[cur].rpos].size + 1;
}
valgrind --tool=callgrind --cache-sim ./a.out right
==11493== I   refs:      427,444,674
==11493== I1  misses:          2,288
==11493== LLi misses:          2,068
==11493== I1  miss rate:        0.00%
==11493== LLi miss rate:        0.00%
==11493== 
==11493== D   refs:      213,159,341  (144,095,416 rd + 69,063,925 wr)
==11493== D1  misses:     15,401,346  ( 12,737,497 rd +  2,663,849 wr)
==11493== LLd misses:        329,337  (      7,935 rd +    321,402 wr)
==11493== D1  miss rate:         7.2% (        8.8%   +        3.9%  )
==11493== LLd miss rate:         0.2% (        0.0%   +        0.5%  )
==11493== 
==11493== LL refs:        15,403,634  ( 12,739,785 rd +  2,663,849 wr)
==11493== LL misses:         331,405  (     10,003 rd +    321,402 wr)
==11493== LL miss rate:          0.1% (        0.0%   +        0.5%  )
valgrind --tool=callgrind --cache-sim=yes ./a.out left

==11496== I   refs:      418,204,722
==11496== I1  misses:          2,327
==11496== LLi misses:          2,099
==11496== I1  miss rate:        0.00%
==11496== LLi miss rate:        0.00%
==11496== 
==11496== D   refs:      204,114,971  (135,076,947 rd + 69,038,024 wr)
==11496== D1  misses:     19,470,268  ( 12,661,123 rd +  6,809,145 wr)
==11496== LLd misses:        306,948  (      7,935 rd +    299,013 wr)
==11496== D1  miss rate:         9.5% (        9.4%   +        9.9%  )
==11496== LLd miss rate:         0.2% (        0.0%   +        0.4%  )
==11496== 
==11496== LL refs:        19,472,595  ( 12,663,450 rd +  6,809,145 wr)
==11496== LL misses:         309,047  (     10,034 rd +    299,013 wr)
==11496== LL miss rate:          0.0% (        0.0%   +        0.4%  )