异步写入位数组

时间:2019-04-02 13:33:12

标签: c++ multithreading c++11 bit-manipulation

TL; DR 如何安全地对A[n/8] |= (1<<n%8);进行一次位更新A,因为charn的大数组(即,设置A位使用C ++ 11的<thread>库并行计算时,{{1}的是真的)?


我正在执行易于并行化的计算。我正在计算自然数的某个子集的元素,并且我想找到子集中不是 的元素。为此,我创建了一个巨大的数组(例如A = new char[20l*1024l*1024l*1024l],即20GiB)。如果n位于我的集合中,则此数组的n为真。

在并行执行并使用A[n/8] |= (1<<n%8);将位设置为true时,我似乎丢失了一些信息,这可能是由于在{{1的相同 byte }}(每个线程必须先读取字节,更新单个位并将字节写回)。我该如何解决?有没有办法将更新作为原子操作进行?

代码如下。 GCC版本:A。该计算机是8核Intel®Xeon®CPU E5620 @ 2.40GHz,37GB RAM。编译器选项:g++ (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609

g++ -std=c++11 -pthread -O3

#include <iostream> #include <thread> typedef long long myint; // long long to be sure const myint max_A = 20ll*1024ll*1024ll; // 20 MiB for testing //const myint max_A = 20ll*1024ll*1024ll*1024ll; // 20 GiB in the real code const myint n_threads = 1; // Number of threads const myint prime = 1543; // Tested prime char *A; const myint max_n = 8*max_A; inline char getA(myint n) { return A[n/8] & (1<<(n%8)); } inline void setAtrue(myint n) { A[n/8] |= (1<<n%8); } void run_thread(myint startpoint) { // Calculate all values of x^2 + 2y^2 + prime*z^2 up to max_n // We loop through x == startpoint (mod n_threads) for(myint x = startpoint; 1*x*x < max_n; x+=n_threads) for(myint y = 0; 1*x*x + 2*y*y < max_n; y++) for(myint z = 0; 1*x*x + 2*y*y + prime*z*z < max_n; z++) setAtrue(1*x*x + 2*y*y + prime*z*z); } int main() { myint n; // Only n_threads-1 threads, as we will use the master thread as well std::thread T[n_threads-1]; // Initialize the array A = new char[max_A](); // Start the threads for(n = 0; n < n_threads-1; n++) T[n] = std::thread(run_thread, n); // We use also the master thread run_thread(n_threads-1); // Synchronize for(n = 0; n < n_threads-1; n++) T[n].join(); // Print and count all elements not in the set and n != 0 (mod prime) myint cnt = 0; for(n=0; n<max_n; n++) if(( !getA(n) )&&( n%1543 != 0 )) { std::cout << n << std::endl; cnt++; } std::cout << "cnt = " << cnt << std::endl; return 0; } 时,我得到正确的值n_threads = 1。当cnt = 29289时,我在两个不同的调用中分别得到了n_threads = 7cnt = 29314,这表明对一个字节的一些按位运算是同时发生的。

1 个答案:

答案 0 :(得分:5)

std::atomic在这里提供您需要的所有功能:

std::array<std::atomic<char>, max_A> A;

static_assert(sizeof(A[0]) == 1, "Shall not have memory overhead");
static_assert(std::atomic<char>::is_always_lock_free,
              "No software-level locking needed on common platforms");

inline char getA(myint n) { return A[n / 8] & (1 << (n % 8)); }
inline void setAtrue(myint n) { A[n / 8].fetch_or(1 << n % 8); }

getA中的负载是原子(equivalent to load()),std::atomic甚至还内置支持or将存储的值与另一个值({{3 }}),当然是原子的。

在初始化A时,for (auto& a : A) a = 0;的幼稚方式将要求在每个存储之后进行同步,您可以通过放弃某些线程安全性来避免这种情况。 fetch_or仅要求我们编写的内容对其他线程可见(但对我们而言其他线程的写入不可见)。确实,如果您这样做

// Initialize the array
for (auto& a : A)
  a.store(0, std::memory_order_release);

无需在x86上进行任何程序集级同步,即可获得所需的安全性。您可以在线程完成后对负载进行相反的操作,但这在x86上没有任何额外的好处(两种方式都只是mov)。

演示完整代码:std::memory_order_release