用零并行填充 std::vector
Posted
技术标签:
【中文标题】用零并行填充 std::vector【英文标题】:Parallel fill std::vector with zero 【发布时间】:2017-02-04 20:00:06 【问题描述】:我想用 openmp 将 std::vector<int>
填充为零。如何快速做到这一点?
我听说循环遍历向量以将每个元素设置为零很慢,而std::fill
要快得多。现在还是这样吗?
Fastest way to reset every value of std::vector<int> to 0
我是否必须手动将std::vector<int>
划分为区域,在每个线程上使用#pragma omp for
循环,然后在循环中使用std::fill
?
【问题讨论】:
是的,它仍然是真的。是的,如果您使用的是 OpenMP,您必须自己分配作业。无论哪种方式,如果你关心它的速度,你应该测量它。我建议分配 2 个大小的作业的功率(引用的线程说它在 MMX 寄存器中使用了 16 个整数,这可能是最小的可行作业大小)。根据向量的长度,单线程填充可能会更快。一定要测量并找到交叉点。这是一条评论,因为它并不能很好地回答您的问题。这只是你自己可能有过的沉思。 GCC 6.3 和 Clang 3.9.0 都将“循环并在各处分配 0”和std::fill
编译为对 memset
的(尾)调用。这不是完全相同的代码,但繁重的工作是相同的。
我打赌用零填充向量会占用您最少的时间。在您有证据表明这是问题区域之前,请不要担心。
@voo,“我开枪了,没有测试。”编译器。这就是为什么您应该始终进行测试的原因。现在,如果你原谅我,我得再吃点我的脚。
@sbabbi,这只是将实际工作转移到以后。由于局部性,可能很好,但也可能对所有页面错误都不利。
【参考方案1】:
您可以将向量拆分为块,以便每个线程填充std::fill
:
#pragma omp parallel
auto tid = omp_get_thread_num();
auto chunksize = v.size() / omp_get_num_threads();
auto begin = v.begin() + chunksize * tid;
auto end = (tid == omp_get_num_threads() -1) ? v.end() : begin + chunksize);
std::fill(begin, end, 0);
您可以通过将chunksize
舍入到最接近的缓存线/内存字大小(128 字节 = 32 int
s)来进一步改进它。假设v.data()
的对齐方式类似。这样,您就可以避免任何虚假分享问题。
在双插槽 24 核 Haswell 系统上,我获得了接近 9 倍的加速:1 个线程为 3.6 秒,24 个线程为 0.4 秒,4.8B 整数 = ~48 GB/秒,结果略有不同,并且这不是科学分析。但是离系统的内存带宽也不算太远。
对于一般性能,您应该关注将向量划分为不仅用于此操作,还用于进一步操作(无论是读取还是写入),如果可能的话。这样,如果您需要数据,或者至少在同一个 NUMA 节点上,您就可以增加数据实际在缓存中的机会。
奇怪的是,在我的系统上,std::fill(..., 1);
对于单线程比 std::fill(..., 0)
快,但对于 24 线程则慢。都使用 gcc 6.1.0 和 icc 17.0.1。我想我会把它发布到一个单独的问题中。
【讨论】:
一目了然,当向量的大小不能被线程数整除时,这似乎不是线程之间的最佳计算划分。最后一个线程的工作量可能比其他线程少。感谢omp parallel
的好例子。我不知道我们可以这样使用 tid,并且在我的代码中的每个线程上仍然有 omp 循环...
没有最后一个线程得到更多的工作,但最多nthreads - 1
更多,可以忽略不计。或者你可以做chunksize = (v.size() - 1) / nthreads + 1
,这更平衡一点。但我会争辩(去编辑那个),对齐块实际上更重要。
对齐块是什么意思?
@rxu,你看到我回答的编辑了吗?还可以看看:en.wikipedia.org/wiki/… 这说明清楚了吗?
是的。我现在明白了。以上是关于用零并行填充 std::vector的主要内容,如果未能解决你的问题,请参考以下文章
为啥 C++11 会从 std::vector 填充构造函数的原型中移除默认值?
为什么c ++用零来初始化std :: vector,而不是std :: array?