背景:所以我正在研究一个光线跟踪器...为了构建空间分区方案,我最初有一些像这样的代码:
if (msize <= 2) { // create a leaf node
Model **models = new Model*[msize];
for (uint i=0; i<msize; ++i)
models[i] = &mlist[i];
*arrayPtr = Node(models, msize); // class Node contains a copy of models
... increment arrayPtr ...
return;
}
基本上,在构造这个空间分区树之后,光线遍历树寻找模型,模型全部存储在一个大数组中。叶子节点包含指向模型指针的指针。
然后我意识到嘿,我没有理由加上额外的间接水平;如果我正确安排我的模型,我可以让叶子节点直接指向大型模型。大数组中彼此相邻的模型都属于给定的叶节点,因此叶子将包含指向模型的指针。所以我做了这个,并用其他一切保持不变来测试它。
现在人们会认为这显然会加快计划的进度。好吧,它确实加速了单线程版本(大约10%),但它减慢了多线程版本(大约15%!如果你正在做大量优化,这是非常重要的。)我很喜欢关于如何解决这个问题的失误 - 我认为间接是坏的,我认为减少内存使用是好的,特别是用于多线程...叶子节点或模型没有任何写入,所有写入都完成了单独的数据结构。
关于如何分析问题的任何指示/建议都会很棒。
一些杂项统计:cachegrind告诉我双间接方法的指令引用/缓存未命中次数较少,但更多的数据引用/缓存未命中。但两者的区别并不大。
编辑:根据要求,我关注的数据结构:
class Node {
ushort type;
union {
ushort axisID;
ushort childrenSize;
};
union {
Model **models;
Node *rightChild;
};
float leftPlane, rightPlane;
... public methods and stuff ...
}
我基本上将Model **models
更改为Model *models
,然后我获得速度下降。类Model
本身包含指向两个抽象类的指针Shape
和Material
。这里提到的所有类都是块分配的,Material
除外,因为目前我只使用一个。
答案 0 :(得分:1)
我的第一个猜测是你遇到false-sharing。如果您有多个线程同时修改同一缓存行中的内存,则硬件将花费大量时间在处理器之间传递缓存行的所有权。
答案 1 :(得分:1)
另一个人质疑减速是否来自增加的间接性,或者是你如何分配struct Model
的变化。因为您现在将Model
结构分配为连续的内存区域,所以相邻结构可能共享相同的缓存行。如果您的线程同时访问相邻的结构,则会争用访问权限。一个读取访问将在一个总线周期停止,同时等待另一个。
什么是sizeof(class Model)
?您可以尝试使用虚拟变量对其进行扩展,直到该类为高速缓存行的大小。
另一种可能性是您已更改正在访问的成员变量的对齐方式。如果您的sizeof(class Model)
不是机器字长的倍数(例如,8字节),则此类对象的数组将使某些成员与字大小对齐,而某些成员则不对应。未对齐会导致内存总线上的双重读取,因为获取单元从对齐的内存位置读取机器字,并将这两个读取中的寻址值合成。
答案 2 :(得分:0)
我要寻找的最重要的事情是一些不正确的初始化,要么制作重复的数据,要么有不正确的共享数据。这在代码中并不明显,但从**到*时,这是一个明显的错误。