C++:为std::vector分配内存,然后并行初始化其元素

7

我有一个使用案例,需要创建一个 std::vector,其中包含许多元素,每个元素都是一个简单但非原始类型(POD结构)。由于向量和类型足够大/复杂,在下面的情况中,...

std::vector<U> v;
v.resize(1000000000);
for(size_t i=0;i<v.size();++i){/* initialize v[i] */}

resize 调用明显缓慢。而且这种方式很浪费,因为 resize 会默认初始化所有的元素,然后我还需要在一个循环中遍历并将它们全部设置为正确/有用的值。

我希望做的是分配 vector 的所有内存空间,但不初始化任何元素,然后并行地遍历并初始化所有元素,例如使用 OpenMP。

std::vector<U> v;
v.reserve(1000000000);
#pragma omp parallel for
for(size_t i=0;i<v.size();++i){/* initialize v[i] */}

然而,reserve 实际上并没有改变 v 的大小,所以我必须在循环中继续使用 push_back,这将无法保持元素的正确顺序(在我的用例中很重要);我真的想在循环体中编写类似于 v[i] = ... 的代码。
有没有一种方法可以分配/“初始化”一个向量,而不初始化任何元素,然后并行填充/初始化所有元素?

如果您知道所需的大小,为什么不使用传统的数组呢? - Born2Smile
@Born2Smile 一个这么大的数组会带来自己的问题,特别是如果它太大,你可能会溢出堆栈。 - user10957435
1
@Chipster,所以不要将它放在堆栈上。auto v = std::make_shared<U[]>(1000000000) - Born2Smile
2
@Born2Smile,嗯,确实如此。但这不是唯一的问题。还存在一个问题,即dynamically allocated and normal arrays will still default construct,而OP表达了不想这样做的兴趣。 - user10957435
你可以使用 new 分配数据,以并行方式进行初始化(以获得首次触碰 NUMA 友好行为),然后按照 https://dev59.com/snE95IYBdhLWcg3wJKl_ 中所述将其转换为 std::vector - Jeff Hammond
显示剩余3条评论
2个回答

4
您的选择有:
  • std::vector 替换为另一种容器(例如uvector
  • 使用某种库来进行大小调整而不进行初始化,例如来自Facebook的UninitializedMemoryHacks
在您执行大小调整后,可以按照通常的方式使用OpenMP。

3

这取决于类型U的默认构造函数。如果默认构造函数很便宜,那么并行化很可能不会有任何收益。

struct U {
   int a, b, c;
   U():a(0), b(1), c(2) {}
};

如果你的默认构造函数很耗费时间,将它拆成两部分可能更合理:一个用于默认初始化,另一个用于实际初始化的函数。

struct U {
   vector<int> a;
   U() {}
   void init(int n) { a.resize(n); }
};

在两种选择中,正常的向量大小调整或赋值调用都很难被超越。

如果您真的想以这种方式做事情,您可以使用reinterpret_cast到数组。这样,就不会调用默认构造函数。

U * u_array = reinterpret_cast<U*>(malloc(100*sizeof(U)));

我强烈反对选择最后一种选项。


1
请注意,malloc 解决方案理想情况下应该只包含 POD 类型的 struct U。由于 U 包含了一个列出的 std::vector,在调用 free 释放您的 malloc 数组之前,您必须跳过一些额外的步骤来释放 Uvector 持有的资源。更不用说在数组中构造 U 的步骤了。 - Larry B
1
你是完全正确的。当你使用reinterpret_cast时,你确实需要知道自己在做什么。 - mentatkgs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接