OpenMP 并行区域中的 std::vector push_back 会导致错误共享吗?
Would this std::vector push_back in OpenMP parallel region result in false-sharing?
下面的示例代码是我的工作代码的简化版本。在此代码中,写入共享变量仅在调用 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 上)
我不是 std::vector 实现方面的专家,但我很确定它不会检查是否有另一个进程正在同时写入。
不过,这里有两个建议:
是的,关键操作的开销很小,但与并行执行 'some_heavy_work' 的收益相比可以忽略不计(我猜……)。所以,有疑问,我会把它留在那里
你应该检查关键和原子之间的区别 (openMP, atomic vs critical?)。
希望对您有所帮助
鉴于您的FortyByteStruct
可能小于缓存行(通常为 64 字节),因此在写入结果数据时可能会出现错误共享。然而,这几乎没有什么意义,因为它会被关键部分的成本所掩盖 - 以及修改 vector
本身(不是它的数据)的 "true" 共享。您不需要知道 std::vector
的实现细节——只需要知道它的数据在内存中是连续的并且它的状态(指向 data/size/capacity 的指针)在内存中矢量变量本身。当多个线程以不受保护的方式访问同一缓存行上的单独数据时,通常会出现错误共享问题。请记住,虚假共享不会影响正确性,只会影响性能。
假共享的一个稍微不同的例子是当你有一个 std::vector<std::vector<struct FortyByteStruct>>
并且每个线程执行一个不受保护的 push_back
时。我解释的很详细.
在您的示例中,已知向量的总大小,最好的方法是在循环之前调整向量的大小,然后只分配 results[i] = result
。这避免了关键部分,并且 OpenMP 通常以几乎没有错误共享的方式分布循环迭代。您还会得到 results
.
的确定性顺序
也就是说,当你通过测量确认时间是由some_heavy_work
支配的时候,你就可以了。
下面的示例代码是我的工作代码的简化版本。在此代码中,写入共享变量仅在调用 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 上)
我不是 std::vector 实现方面的专家,但我很确定它不会检查是否有另一个进程正在同时写入。
不过,这里有两个建议:
是的,关键操作的开销很小,但与并行执行 'some_heavy_work' 的收益相比可以忽略不计(我猜……)。所以,有疑问,我会把它留在那里
你应该检查关键和原子之间的区别 (openMP, atomic vs critical?)。
希望对您有所帮助
鉴于您的FortyByteStruct
可能小于缓存行(通常为 64 字节),因此在写入结果数据时可能会出现错误共享。然而,这几乎没有什么意义,因为它会被关键部分的成本所掩盖 - 以及修改 vector
本身(不是它的数据)的 "true" 共享。您不需要知道 std::vector
的实现细节——只需要知道它的数据在内存中是连续的并且它的状态(指向 data/size/capacity 的指针)在内存中矢量变量本身。当多个线程以不受保护的方式访问同一缓存行上的单独数据时,通常会出现错误共享问题。请记住,虚假共享不会影响正确性,只会影响性能。
假共享的一个稍微不同的例子是当你有一个 std::vector<std::vector<struct FortyByteStruct>>
并且每个线程执行一个不受保护的 push_back
时。我解释的很详细
在您的示例中,已知向量的总大小,最好的方法是在循环之前调整向量的大小,然后只分配 results[i] = result
。这避免了关键部分,并且 OpenMP 通常以几乎没有错误共享的方式分布循环迭代。您还会得到 results
.
也就是说,当你通过测量确认时间是由some_heavy_work
支配的时候,你就可以了。