在多线程中初始化向量

Initializing vector in multithread

本文关键字:向量 初始化 多线程      更新时间:2023-10-16

我有一个大型向量std::vector<some_class> some_vector,我需要用不同的构造函数some_class(constructor_parameters)初始化。正常的方法是:

std::vector<some_class> some_vector
some_vector.reserve(length)
for (...) some_vector.push_back(some_class(constructor_parameters))

但是,由于这个向量很大,所以我想并行执行此操作。是否有任何方法可以将向量和 push_back在q的不同位置分开,以便每个线程可以开始初始化向量的不同部分?

我通过分裂/加入向量阅读了一些答案,但尚未发现任何有用的答案。由于我的向量确实很大,因此我必须避免使用为每个线程创建新向量,然后将它们复制到原始的矢量 - 我只能使用一大块内存。

我尝试使用some_vector.at(some_loc) = some_class(constructor_parameters),但这不是与非初始化的向量一起使用的。

我可以将向量初始化为某些转储值,然后使用at将其启动到适当的值,但不是有效的。

所以我的问题 - 如何有效地(就内存消耗和计算时间而言)初始化大型向量?

编辑:回答评论:

size - 在程序运行期间,容器不会更改其大小,但是在编译时,大小不知道。大小很大,因为这只是问题的范围 - 我正在执行宇宙学n体模拟,其中粒子/网格细胞的数量很容易被1024^3及更多。

ctors - 现在它们只是为类成员分配值(3〜7分配),但我打算添加一些计算

成员 - 很容易共配,通常为2 std::vector(3)

为什么向量 - 我最初仅使用基本类型数组和new / delete指令。我想使用矢量,因为它们的各种功能,自动内存(DE)分配,使用迭代器等。我只是假设它们应该易于使用所有其他好的属性实现到多线程中...

对于常规类型T,您描述的问题是要跟踪已构建的T中的哪个并且没有。

如果您将"这是有效的值"数据压缩到比特字段中,则检查有效性是非常不连贯的。

一种简单的方法是C 17中的vector<optional<T>>或使用Boost。预先示例(到nullopt),然后使用emplace在您想要的任何线程中构造术语。

最后,考虑不使用单个向量。写一个包装器,将多个向量拼接到一个可见的容器中。

我能想到两种解决方案。

一种是使用并发数据结构,例如TBB的concurrent_vector。查看其文档

第二个是编写一个自定义分配器,当无参数调用时,其construct成员不会调用默认构造函数。然后将向量分配一次,然后并行初始化每个元素。需要注意确保这样的construct不会稍后会引起麻烦。如果您可以在构造新元素时使用C 11 MOVE语义,则它将最有效。在这种情况下,您在主线程中唯一需要做的就是分配内存,这是您几乎无法避免任何方式的成本。

这是一个具体的例子,

template <std::size_t n, std::size_t m>
class StirlingMatrix2
{
    public:
    StirlingMatrix2()
    {
        // compute the matrix of Stirling numbers
        // relatively expensive
    }
    double operator()(std::size_t i, std::size_t j) const
    {
        return data_[i * (m + 1) + j];
    }
    private:
    double data_[(n + 1) * (m + 1)];
}; // class StirlingMatrix
template <typename T, bool InvokeConstructor = true>
class Allocator
{
    public:
    // other member functions and types
    template <typename U>
    void construct(U *ptr)
    {
        construct_dispatch(
            ptr, std::integral_constant<bool, InvokeConstructor>());
    }
    template <typename U, typename Arg, typename... Args>
    void constrct(U *ptr, Arg &&arg, Args &&... args)
    {
        std::allocator<T>::construct(
            ptr, std::forward<Arg>(arg), std::forward<Args>(args)...);
    }
    private:
    template <typename U>
    void construct_dispatch(U *ptr, std::true_type)
    {
        std::allocator<T>::construct(ptr);
    }
    template <typename U>
    void construct_dispatch(U *, std::false_type)
    {
    }
}; // class Allocator
// specialization for void type is needed
int main()
{
    constexpr n = 100;
    constexpr m = 100;
    using T = StirlingMatrix2<n, m>;
    // each element will be constructed, very expensive
    std::vector<T> svec1(N);
    // allocate memory only
    std::vector<T, Allocator<T, false>> svec2(N);
    // within each thread
    // invoke inplace new to construct elements
}

为此,不能有指针成员或班级成员并不小。否则,没有办法安全。如果您不必使用容器类,可以使用类似的技术,而是通过malloc手动管理内存等等。