如何并行化此求解器代码

时间:2011-06-20 12:10:18

标签: c++ multithreading algorithm performance

我试图优化和并行化一些代码,这些代码在带有链接和节点的图形结构中进行模拟。一个主要的热点是像这样的循环:

void ExecuteAll()
{
    for(long i = TotalCount(); i >= 1; i--)
    {
        long k    = linkOrder[i];
        long link = firstLink[k];
        if (link == 0)
            continue;

        double d = 0.;
        for(; link != 0; link = nextLink[link])
        {
            long kk  = getNode(link);
            d  += fak[link]*res[kk];
        }
        d += res[k];
        double d2 = d/fak2[k];
        res[k]   = d2;
        res2[k] += d2;
    }
}

我通过实现这样的类来重写这个以使用多个线程:

class myDemoClass
{    
    bool volatile *isDone;
public:

    void ExecuteSlice() 
    {   
        for(long i = TotalCount() - mThreadIndex; i >= 1; i -= threadCount)
        {
            long k = linkOrder[i];
            Execute(k);
        }
    }

    void Execute(long k)
    {
        long link = firstLink[k];
        if (link == 0)
        {
            isDone[k] = true;
            return;
        }
        double d = 0.;
        for(; link != 0; link = nextLink[link])
        {
            long kk  = getNode(link);

            for(int x = 0; ! isDone[kk]; x++)
                {} // Wait until data is ready. Time too short for sleep or event

            d  += fak[link]*res[kk];
        }
        d += res[k];
        double d2 = d/fak2[k];
        res[k]   = d2;
        res2[k] += d2;

        isDone[k] = true;
    }
}

我为每个线程创建了这个类的实例,其中每个线程对i的所有值的一片进行操作。我引入了一个新数组bool volatile *isDone,因为我不能使用未处理节点的结果。我尝试插入一个睡眠或事件而不是for(;...;){}循环,但事实证明等待状态太短了。

这似乎工作正常。所有对Execute()的调用只有10%必须进入等待循环,因为图形从起点开始越来越多,结果是正确的。

但令人惊讶的是,当使用8个线程的新代码时,8核XEON机器上没有可测量的性能增益(或损失)。我知道这个代码在缓存失效方面并不是最优的,主要是isDone res从多个内核写入和读取{{1}}。但在大多数情况下,取消引用的索引相距很远。该图表有大约1.000.000个节点和链接。

如何改善此代码的并行性?

3 个答案:

答案 0 :(得分:5)

你不能像这样使用volatile来使代码线程安全。 Volatile帮助单线程应用程序通过确保始终重新读取值来将变量映射到外部设备,但是对于线程来说它不够,因为编译器不仅可以重新排序语句,而且处理器可以重新排序指令。您应该为此使用适当的多线程原语,或者由库提供,或者是编译器特定的实现(例如,Win32上的互锁函数,或者gcc上的Atomic Builtins)。同样地,不清楚任何其他数据结构对于多线程修改是安全的。

至于性能,很难说出问题是什么,因为我们对你的图形结构一无所知,而且你的代码太抽象而无法解决它。但是,您似乎花了很多时间来迭代可能已经处理过的链接。理想情况下,您可以反过来这样做,然后从处理没有依赖关系的链接开始,然后在完成时,从依赖于此链接的链接开始,依此类推,这意味着没有等待。也许类似拓扑的东西在这里会有所帮助。

答案 1 :(得分:0)

OpenMP事实上的标准,在当前编译器中得到很好的支持,用于将阵列处理代码并行化为结构相同的任务,您只希望在多个内核上运行而不必担心很多关于线程管理。看一下,它可能有助于澄清你的想法,甚至可以解决问题。

这最适合用于纯粹的CPU绑定任务 - 我不清楚你是否因为你提到等待数据到达而是这种情况?在这种情况下,多线程逻辑可能没有你期望或希望的那么多。

答案 2 :(得分:0)

一些可能有用的想法:

  • 为了确保您的线程代码能够正常工作,我将使用一些已知可以并行化的计算进行测试(作为一个例子,没有立即想到的东西)。然后,您可以测试单线程和多线程实现,并确保整个线程应用程序按预期工作。
  • 您确定您的算法可以很好地并行化甚至完全并行化吗?正如Steve所提到的,CPU绑定计算最适合并行性,而代码看起来像是CPU和IO的混合。根据{{​​1}}您可能是IO绑定的内容,这将限制您可以通过使用多线程算法获得的内容。对代码进行概要分析和基准测试将有助于确定可以应用优化的最佳收益。
  • 请勿将getNode()用于多线程同步,如同其他提到的海报一样。它现在可能确实适合你,但不能保证它将来不会破坏。考虑一下最糟糕的情况,即从现在开始几个月后,它会略微破坏你的所有模拟结果但不足以显而易见。
  • for“wait”循环也是可疑的,应该更改为正确的线程等待。当一个线程在这个循环中“等待”时,它占用了CPU时间,这可以通过在另一个线程中进行实际工作来更好地使用。如果这个等待循环仅在10%的时间内被使用,那么你的收益(如果有的话)会很小但是不能保证它的使用总是这么小。