有没有办法使用原子操作从多个线程更新最大值?
说明性示例:
std::vector<float> coord_max(128);
#pragma omp parallel for
for (int i = 0; i < limit; ++i) {
int j = get_coord(i); // can return any value in range [0,128)
float x = compute_value(j, i);
#pragma omp critical (coord_max_update)
coord_max[j] = std::max(coord_max[j], x);
}
在上面的例子中,临界区同步访问整个向量,而我们只需要独立地同步对每个值的访问。
答案 0 :(得分:5)
根据评论中的建议,我找到了一个不需要锁定的解决方案,而是使用了std :: atomic / boost :: atomic中的比较和交换功能。我仅限于C ++ 03所以在这种情况下我会使用boost :: atomic。
BOOST_STATIC_ASSERT(sizeof(int) == sizeof(float));
union FloatPun { float f; int i; };
std::vector< boost::atomic<int> > coord_max(128);
#pragma omp parallel for
for (int i = 0; i < limit; ++i) {
int j = get_coord(i);
FloatPun x, maxval;
x.f = compute_value(j, i);
maxval.i = coord_max[j].load(boost::memory_order_relaxed);
do {
if (maxval.f >= x.f) break;
} while (!coord_max[j].compare_exchange_weak(maxval.i, x.i,
boost::memory_order_relaxed));
}
将浮点值放入int中有一些样板,因为看起来原子浮点数不是无锁的。我不是100%使用内存顺序,但限制性最小的“放松”级别似乎没问题,因为不涉及非原子内存。
答案 1 :(得分:1)
如何声明长度为128的std::vector<std::mutex>
(或boost::mutex
),然后使用j
元素创建锁定对象?
我的意思是,像:
std::vector<float> coord_max(128);
std::vector<std::mutex> coord_mutex(128);
#pragma omp parallel for
for (int i = 0; i < limit; ++i) {
int j = get_coord(i); // can return any value in range [0,128)
float x = compute_value(j, i);
std::scoped_lock lock(coord_mutex[j]);
coord_max[j] = std::max(coord_max[j], x);
}
或者,根据Rahul Banerjee's suggestion #3:
std::vector<float> coord_max(128);
const int parallelism = 16;
std::vector<std::mutex> coord_mutex(parallelism);
#pragma omp parallel for
for (int i = 0; i < limit; ++i) {
int j = get_coord(i); // can return any value in range [0,128)
float x = compute_value(j, i);
std::scoped_lock lock(coord_mutex[j % parallelism]);
coord_max[j] = std::max(coord_max[j], x);
}
答案 2 :(得分:1)
不确定语法,但在算法上,您有三种选择:
锁定整个向量以保证原子访问(这是您目前正在进行的操作)。
锁定单个元素,以便可以独立于其他元素更新每个元素。优点:最大并行度;缺点:需要很多锁!
介于两者之间!从概念上考虑将您的矢量划分为16(或32/64 / ...)“银行”,如下所示: bank0由向量元素0,16,32,48,64,...组成。 bank1由向量元素1,17,33,49,65,...组成。 bank2由向量元素2,18,34,50,66,...组成。 ... 现在,在访问元素之前使用16个显式锁,并且可以具有最多16路并行性。要访问元素n,获取锁定(n%16),完成访问,然后释放相同的锁定。
答案 3 :(得分:1)
为了增加我的两分钱,在开始更精细的优化之前,我会尝试以下方法,无需omp critical
:
std::vector<float> coord_max(128);
float fbuffer(0);
#pragma omp parallel
{
std::vector<float> thread_local_buffer(128);
// Assume limit is a really big number
#pragma omp for
for (int ii = 0; ii < limit; ++ii) {
int jj = get_coord(ii); // can return any value in range [0,128)
float x = compute_value(jj,ii);
thread_local_buffer[jj] = std::max(thread_local_buffer[jj], x);
}
// At this point each thread has a partial local vector
// containing the maximum of the part of the problem
// it has explored
// Reduce the results
for( int ii = 0; ii < 128; ii++){
// Find the max for position ii
#pragma omp for schedule(static,1) reduction(max:fbuffer)
for( int jj = 0; jj < omp_get_thread_num(); jj++) {
fbuffer = thread_local_buffer[ii];
} // Barrier implied here
// Write it in the vector at correct position
#pragma omp single
{
coord_max[ii] = fbuffer;
fbuffer = 0;
} // Barrier implied here
}
}
请注意,我没有编译代码段,因此我可能在内部留下了一些语法错误。无论如何,我希望我已经传达了这个想法。