C 二叉树的缓存局部性

C 二叉树的缓存局部性,c,caching,C,Caching,如果我有一棵像下面这样的树 结构树{ //资料 左树; 树是对的; }; 我想开始为树叶分配内存,有没有办法确保当我遍历树时树叶被缓存?如果我使用malloc,那么我认为叶子会分散在堆中,并且每次尝试访问时都会出现缓存未命中。在选择的平台上,可能可以提高缓存命中率,但当然,几乎不能保证每次运行都能获得一致的成功 但让我们尝试一些想法: 创建tree\u alloc()和tree\u free()在一个组中分配/管理多个struct tree\u t,第一次调用时为256个,然后分配给下一个25

如果我有一棵像下面这样的树

结构树{ //资料 左树; 树是对的; }; 我想开始为树叶分配内存,有没有办法确保当我遍历树时树叶被缓存?如果我使用malloc,那么我认为叶子会分散在堆中,并且每次尝试访问时都会出现缓存未命中。

在选择的平台上,可能可以提高缓存命中率,但当然,几乎不能保证每次运行都能获得一致的成功

但让我们尝试一些想法:

  • 创建
    tree\u alloc()
    tree\u free()
    在一个组中分配/管理多个
    struct tree\u t
    ,第一次调用时为256个,然后分配给下一个255个分配。这将使随机分配/释放调用复杂化,但如果树很大且其生长/收缩均匀,则可能值得付出努力

  • 使树变小。使数据成为指针

    struct tree_t {
      data_T *data
      tree_t *left;
      tree_t *right;
     };
    

  • 哎呀!GTG-将使此wiki

    malloc
    不保证内存将分配到何处。如果您希望数据被并置以利用缓存位置,一个简单的替代方法是分配一个结构数组,然后从该数组(即对象池)进行分配。这基本上类似于编写自己的内存分配器,只是由于每个元素的大小相同而大大简化了。如果您知道所需元素的最大数量,那么您就不必添加增加“内存池”大小的功能,这也将大大简化工作。如果您的分配/自由函数由不同的线程访问,您还必须考虑线程安全性。还有很多其他的事情要考虑,这只是少数。< /P> 注意:正如其他人在评论中所说的那样,过早优化通常不值得,或者更糟糕的是会适得其反,但如果你想尝试,这是一种方法


    这是一个关于对象池的有用链接

    将数据结构逻辑与内存分配逻辑分开,优化代码时不会出现任何问题

    例如,您的
    add
    函数不应包含
    malloc
    或任何
    free
    。思考
    sprintf
    是如何运作的;第一个参数是指向将写入字符串的位置的指针。因此,使您的
    添加
    函数如下所示:

    int add(struct tree *destination, struct tree *source) {
        // XXX: Add source into destination
    }
    
    。。。并在调用
    add
    之前分配
    source
    。通过这种方式,您可以选择自动存储持续时间(例如,
    struct tree foo[128];
    ,作为一个数组,应该很好并且缓存友好),或者您可以选择使用
    malloc
    一次分配一个节点(不支持缓存),或者您可以选择使用
    malloc
    分配大量节点组(应该是缓存友好的)…这给了你优化的空间,是吗


    只有在确定代码太慢并且探查器告诉您代码慢的原因后,您才应该进行优化。

    此代码没有举例说明世界上最好的软件工程实践,但符合您的要求:

    tree_t *garbage = NULL;
    
    tree_t *alloc_tree_t() {
        if (garbage == NULL) {
            tree_t *nodearr = malloc(sizeof(tree_t)*1024);
            for(int i = 0; i < 1023; i++) {
                nodearr[i]->left = &(nodearr[i+1]);
            }
            garbage = &(nodearr[0]);
        }
        tree_t *tmp = garbage;
        garbage = tmp->left;
        tmp->left = tmp->right = NULL;
        return tmp;
    }
    
    void free_tree_t(tree_t *p) {
        p->left = garbage;
        garbage = p;
        p->right = NULL;
    }
    
    tree\u t*垃圾=NULL;
    tree_t*alloc_tree_t(){
    if(垃圾==NULL){
    树节点=malloc(树节点的大小)*1024);
    对于(int i=0;i<1023;i++){
    节点耳环[i]->左=&(节点耳环[i+1]);
    }
    垃圾=&(noderar[0]);
    }
    tree_t*tmp=垃圾;
    垃圾=tmp->左;
    tmp->left=tmp->right=NULL;
    返回tmp;
    }
    无效自由树(树*p){
    p->left=垃圾;
    垃圾=p;
    p->right=NULL;
    }
    
    其他人给出了正确的答案,一个固定大小的池(),但还有一些额外的注意事项需要更透彻的解释。使用内存池分配的叶子仍有可能或甚至可能具有较低的缓存命中率。在64字节边界上对齐的8*n树块是理想的,尽管n==1024以上没有好处

    另请注意,看看Judy阵列,它是一种缓存优化的树状数据结构()

    (简要地)回顾一下是有帮助的高速缓存的工作原理。高速缓存被分成若干固定大小的行。通常一级缓存的行大小为64字节;Intel和AMD已经使用64字节一级缓存15年了,现代ARM处理器(如A15)也使用64字节行。关联性决定了一组缓存对应的行数。当数据被带进高速缓存时,一些缓存会出现错误nction将地址散列到一个集合中。数据可以存储在集合中的任何一行中。例如,在双向集合关联缓存中,任何给定地址都可以存储在两个可能的缓存行之一

    最大化可缓存性包括减少缓存线回迁:
    1.将数据组织到缓存线大小的块中。
    2.在缓存线边界上对齐块。
    3.在映射到不同缓存集的地址处分配块。
    4.在同一缓存线中存储具有时间局部性(即大约在同一时间访问)的数据。
    5.尽可能减小数据大小以增加密度

    如果不执行(1),则回迁将带来附近可能不可用的数据,从而减少您关心的数据空间量。如果不执行(2),则对象可能跨越缓存线,需要两倍的回迁。如果不执行(3),则某些缓存集可能未充分利用,其效果与(1)类似。如果您不这样做(4),则即使您正在最大限度地提高缓存利用率,但获取的大多数数据在获取时都没有用处,并且该行可能在数据变得有用之前被逐出。(5)通过将对象打包到较少的空间中来增加适合缓存的对象数。例如,如果您可以保证具有少于2^32个叶子,则可以将uint32索引存储到树[]