神秘的指针相关多线程减速问题

5

背景: 我正在开发一个光线追踪器.. 对于我构建的空间划分方案,我最初有一些代码如下:

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 本身包含指向两个抽象类 ShapeMaterial 的指针。所有提到的类都是块分配的,除了 Material,因为目前我只使用了一个。


你能否发布一下你正在比较的两个数据结构的版本? - Greg Rogers
别名问题,也许是吗?当我在C/C++中看到联合时,这几乎总是我的第一个想法。分析器指出哪个具体函数变慢了?您是否查看了反汇编以查看是否存在任何差异? - jalf
你如何在线程之间共享数据? - Malkocoglu
还有一个额外的实验可以尝试。你说过你不得不重新排序模型,以使用指向模型的指针而不是指向模型数组的指针。你能否测试一下使用指向模型数组的指针,但是采用新的组织方式呢?(即数组中的指针将是连续的) - Bahbar
3个回答

1

我的第一个猜测是你遇到了false-sharing问题。如果你有多个线程同时修改同一缓存行的内存,硬件将花费大量时间在处理器之间传递缓存行的所有权。


但是我的节点和模型都是块分配的... 嗯...在这种情况下,虚假共享会发生吗? - int3
1
据我理解,两个线程都没有修改涉及的数据,这就排除了虚假共享。不过这也是我的第一反应。 - jalf
在同一总线周期内读取相同高速缓存行的读取争用怎么办?我认为L1高速缓存是单端口的。 - Heath Hunnicutt

1

另一个人质疑减速是否来自于添加的间接性,或者是如何分配struct Model的变化。因为您现在正在将Model结构作为连续的内存区域进行分配,所以相邻的结构体可能共享同一缓存行。如果您的线程同时访问相邻的结构体,则它们将争夺访问权。一个读取访问将在等待另一个总线周期时停顿。

sizeof(class Model)是多少?您可以尝试使用虚拟变量扩展它,直到类的大小与您的缓存行大小相同。

另一个可能性是您已更改要访问的成员变量的对齐方式。如果您的sizeof(class Model)不是机器字大小(例如8字节)的倍数,则这样的对象数组将具有某些成员对齐到字大小,而其他成员则没有。不对齐会导致在内存总线上进行双重获取,因为获取单元从对齐的内存位置读取机器字,并将寻址值组合成这两个获取中的一个。


0

我要寻找的最大问题是一些不正确的初始化,可能导致重复数据或不正确的共享数据。虽然代码中没有明显的错误,但在从**到*时犯这个错误非常容易。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接