【问题标题】:Asynchronous writing to a bit array异步写入位数组
【发布时间】:2019-04-02 13:33:11
【问题描述】:

TL; DR 如何安全地为A 执行单个位更新A[n/8] |= (1<<n%8); 是一个巨大的chars 数组(即,设置nbit A true) 使用 C++11 的 <thread> 库进行并行计算时?


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

当并行执行此操作并使用 A[n/8] |= (1<<n%8); 将位设置为 true 时,我似乎会丢失少量信息,这可能是由于 A 的同一 byte 上的并行工作(每个线程必须首先读取字节,更新单个位并将字节写回)。我怎样才能解决这个问题?有没有办法将此更新作为原子操作进行?

代码如下。 GCC 版本:g++ (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609。该机器是 8 核 Intel(R) Xeon(R) CPU E5620 @ 2.40GHz,37GB RAM。编译器选项: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 = 7 时,我在两个不同的调用中得到cnt = 29314cnt = 29321,这表明对单个字节的一些按位操作是同时发生的。

【问题讨论】:

  • @MarekR 这很容易说,你是对的。是的,有更快(理论上)的方法来做到这一点。实际情况是,它们实际上并没有那么快,而且它们需要更多的内存。
  • 在我们有 C++17 并行算法的工作实现之前,对于像你这样的任务来说,OpenMP 更适合具有简单并行循环结构的任务。
  • 查看compare and swap。但我很确定给你这个任务的人希望你能想出一个数学上更聪明的解决方案,而不是对所有输入进行暴力破解。
  • @MaxLanghof 看起来很有希望,我会检查一下。谢谢!
  • (很抱歉在这里与您联系)当您使tex.stackexchange.com/questions/458865/… 更通用时,您能否在我的回答中添加一个简短的免责声明,它指的是该问题的(当时)先前版本?否则可能会令人困惑。提前非常感谢!

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


【解决方案1】:

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 与另一个 (fetch_or) 存储值的支持,当然是原子的。

在初始化A 时,for (auto&amp; a : A) a = 0; 的幼稚方式需要在每次存储之后进行同步,您可以通过放弃一些线程安全来避免这种情况。 std::memory_order_release 只要求我们写入的内容对其他线程可见(但不要求其他线程的写入对我们可见)。事实上,如果你这样做了

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

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

完整代码演示:https://godbolt.org/z/nLPlv1

【讨论】:

  • 看起来不错,谢谢!但是,在我的实际情况中,A 有 20 GiB,所以我必须在堆上动态分配它。
  • @yo' 是的,但这是一个单独的问题。如果您正在执行手动内存管理,那么您并不在乎,但这不是现代 C++。很遗憾,您不能使用std::vector(因为您不能复制std::atomics),但是std::unique_ptr&lt;std::array&lt;...&gt;&gt; 可以解决问题。
  • 天哪。我会是老派,只定义std::array&lt;...&gt; *A 并在任何地方使用(*A) 而不是A:D
  • @yo' 我的意思是,您当前的代码正在泄漏内存。也许你不关心这里,因为程序结束“修复”它,但这不是 stackoverflow 答案应该有 imo 的标准。
  • 我明白你的意思,但对我来说,学习使用另一个 std 功能是一种开销。 “正确”和“交易不多”之间有一个界限:)
猜你喜欢
  • 2019-01-28
  • 2021-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-03
  • 2017-06-07
  • 1970-01-01
相关资源
最近更新 更多