1

下面的示例代码是我的工作代码的简化版本。在此代码中,写入共享变量仅在std::vector::push_back调用的最后一行完成。

std::vector<struct FortyByteStruct> results;

#pragma omp parallel for num_threads(8)
for (int i = 0; i < 250; i++)
{
    struct FortyByteStruct result = some_heavy_work(i);

    #pragma omp critical
    {
        results.push_back(result);
    }
}

我想知道这个push_back操作是否会导致错误共享,让我有机会通过摆脱它来进一步优化。在深入研究这个问题之前,我决定先做一些基准测试。

使用chrono,我分别测量了挂钟some_heavy_work()和临界区的执行时间。后者的执行时间大约是前者的10^(-4)倍,所以我得出的结论是,无论是否涉及虚假共享,优化这部分几乎没有任何好处。

无论如何,我仍然很好奇虚假共享是否是这里的一个问题。我是否必须查看 的内部实现std::vector?任何启示将不胜感激。(我在VS2015上)

4

2 回答 2

3

鉴于您FortyByteStruct可能小于缓存行(通常为 64 字节),写入结果数据时可能存在一些错误共享。然而,这几乎不是必然的,因为它会被关键部分的成本所掩盖 - 以及修改vector自身(而不是数据)的“真正”共享。您不需要知道std::vector's 实现的细节 - 只是它的数据在内存中是连续的,并且它的状态(指向数据/大小/容量的指针)在向量变量本身的内存中。当多个线程以不受保护的方式访问同一高速缓存行上的单独数据时,错误共享通常是一个问题。请记住,虚假共享不会影响正确性,只会影响性能

错误共享的一个稍微不同的例子是当你有一个std::vector<std::vector<struct FortyByteStruct>>并且每个线程都执行一个 unprotected时push_back我在这里详细解释了。

在您的示例中,已知向量的总大小,最好的方法是在循环之前调整向量的大小,然后只分配results[i] = result. 这避免了临界区,并且 OpenMP 通常以几乎没有错误共享的方式分配循环迭代。你也得到一个确定的顺序results

也就是说,当您通过测量确认时间以 为主时some_heavy_work,您就可以了。

于 2017-11-14T11:56:52.590 回答
0

我不是 std::vector 实现方面的专家,但我很确定它不会检查另一个进程是否同时在写入。

不过,这里有两个建议:

  • 是的,关键操作的开销很小,但与并行执行“some_heavy_work”的收益相比,它可以忽略不计(我猜......)。所以,有疑问,我会把它留在那里

  • 您应该检查关键和原子之间的区别(openMP、原子与关键?)。

希望能帮助到你

于 2017-11-14T08:16:38.853 回答