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