push_back 的更快替代方案(已知大小)

Posted

技术标签:

【中文标题】push_back 的更快替代方案(已知大小)【英文标题】:Faster alternative to push_back(size is known) 【发布时间】:2017-07-05 18:58:52 【问题描述】:

我有一个浮点向量。当我处理某些数据时,我会将其推回。我在声明向量时总是知道大小会是多少。

对于最大的情况,它是 172,490,752 个浮点数。 push_back 大约需要 11 秒。

是否有更快的替代方案,例如不同的数据结构或其他什么?

【问题讨论】:

std::vector::reserve 每个push_back 大约需要 35 纳秒(对于 172,490,752)。您正在复制超过一亿个浮点数。 @anc 你在什么上面运行这个,你在运行一个发布版本吗?即使使用ideone 或coliru 等在线编译器,数字也应该比六秒好得多。 else 你在将浮点数推入向量时在做什么? (即他们来自哪里?)。 @pyjg push_back 不是原子的,所以你可以得到比乱序插入更糟糕的事情 @pyjg 使用 2 个线程 A 和 B。A 和 B 分别使用 12 调用 V.push_back()。从 A 的角度来看,V.size() == 0,因此它将1 插入位置0。从 B 的角度来看,由于它与 A 同时执行,V.size() == 0,所以它将2 插入到位置0(实际上,它可能更糟,因为可能会写入不同的字节,你可能会得到一个完全不同的数字;取决于编写 float 是否是原子的)。同时,A 增加 V.size 并且 B 也这样做。在此之后,V.size() 可能是12 或其他,而V[0] 也是未知的 【参考方案1】:

如果您知道最终大小,那么reserve() 在您声明向量后的那个大小。这样它只需要分配一次内存。

此外,您可以尝试使用emplace_back(),尽管我怀疑它对于float 的向量会有什么不同。但是尝试它并对其进行基准测试(当然,使用优化的构建 - 你正在使用优化的构建 - 对吗?)。

【讨论】:

push_backemplace_back 可能仍然比原始访问慢,因为它们必须检查是否需要重新分配,即使没有。性能损失可能很小,但确实存在;) @Tobias 也许。对这两种解决方案进行基准测试是一种判断方法。 我怀疑emplace_back在这里更快,因为浮动是trivially copyable @Daniel H:当我说“我怀疑它会对浮点向量产生任何影响”时,这不是我已经说过的吗?它是微不足道的可复制的事实有点暗示.. 言下之意是相反的:因为它是平凡可复制的,所以复制和移动没有区别,所以不会有任何区别。【参考方案2】:

当您事先知道大小时,加速vector 的常用方法是在使用push_back 之前调用reserve。这消除了每次填满之前的容量时重新分配内存和复制数据的开销。

有时对于要求非常苛刻的应用程序,这还不够。即使push_back 不会重新分配,它仍然需要每次检查容量。没有基准测试就无法知道这有多糟糕,因为现代处理器在总是/从不采用分支时非常高效。

您可以尝试使用resize 代替reserve 并使用数组索引,但resize 强制对每个元素进行默认初始化;如果您知道无论如何都要为每个元素设置一个新值,那就太浪费了。

另一种方法是使用std::unique_ptr<float[]> 并自己分配存储空间。

【讨论】:

您需要使用std::unique_ptr<float[]>,而不是std::unique_ptr<float>,才能获得正确的析构函数行为。 @DanielH 谢谢,我昨天确实自己弄清楚了,但再也没有回来解决问题。现在已经修好了。【参考方案3】:

::boost::container::stable_vector 请注意,分配 172 *4 MB 的连续块可能很容易失败,并且需要大量的页面切换。稳定向量本质上是一系列较小的向量或合理大小的数组。您可能还想并行填充它。

【讨论】:

【参考方案4】:

您可以使用自定义分配器来避免所有元素的默认初始化,如this answer 中所述,与普通元素访问一起使用:

const size_t N = 172490752;
std::vector<float, uninitialised_allocator<float> > vec(N);
for(size_t i=0; i!=N; ++i)
    vec[i] = the_value_for(i);

这避免了(i)默认初始化所有元素,(ii)在每次推送时检查容量,以及(iii)重新分配,但同时保留了使用std::vector(而不是std::unique_ptr&lt;float[]&gt;)的所有便利.但是,allocator 模板参数不常见,因此您需要使用通用代码而不是 std::vector 特定代码。

【讨论】:

【参考方案5】:

我有两个答案:

    正如之前的答案所指出的,使用 reserve 预先分配存储空间会很有帮助,但是: push_back(或emplace_back)本身有性能损失,因为在每次调用期间,他们必须检查向量是否必须重新分配。如果您已经知道要插入的元素数量,则可以通过使用访问运算符[] 直接设置元素来避免这种惩罚

所以我推荐的最有效的方法是:

    使用“填充”构造函数初始化向量:

    std::vector<float> values(172490752, 0.0f);
    

    直接使用访问操作符设置条目:

    values[i] = some_float;
    ++i;
    

【讨论】:

这需要基准测试;这完全有可能会更慢,因为向量必须初始化每个元素,然后再覆盖它。 如果可以在不显式填充向量的情况下初始化存储,这可能是最快的解决方案;)我会先检查程序集输出 @Tobias 你将如何初始化存储而不填充它?您的意思是分配存储而不填充它吗? @FrançoisAndrieux 我认为他的意思是如果std::vector::resize 没有默认初始化,例如。基本上,如果它的行为类似于reserve,但向量的长度也设置为容量的长度。 @Justin 谢谢你的澄清。【参考方案6】:

push_back 速度慢的原因是它需要随着向量的增长多次复制所有数据,即使不需要复制数据也需要检查。向量增长得足够快,以至于这种情况不会经常发生,但它仍然会发生。一个粗略的经验法则是,每个元素平均需要复制一次或两次。早期的元素需要复制更多,但几乎一半的元素根本不需要复制。

您可以通过在创建向量时调用reserve 来避免复制,但不能避免检查,确保它有足够的空间。您可以避免复制和检查,方法是从一开始就以正确的大小创建它,将元素的数量提供给向量构造函数,然后使用索引作为Tobias suggested 插入;不幸的是,这也会通过向量额外的时间来初始化所有内容。

如果您知道编译时的浮点数而不仅仅是运行时,您可以使用std::array,这样可以避免所有这些问题。如果您只知道运行时的数字,我会选择 Mark’s suggestion 与 std::unique_ptr&lt;float[]&gt;。您可以使用

创建它
size_t size = /* Number of floats */;
auto floats = unique_ptr<float[]>new float[size];

你不需要做任何特别的事情来删除它;当它超出范围时,它将释放内存。在大多数情况下,您可以像使用矢量一样使用它,但它不会自动调整大小。

【讨论】:

以上是关于push_back 的更快替代方案(已知大小)的主要内容,如果未能解决你的问题,请参考以下文章

DecimalFormat.format() 的更快替代方案?

iterrows 的更快替代方案

GDI GetPixel() 有更快的替代方案吗?

执行 pandas groupby 操作的更快替代方案

使 mysql 查询更快,或提出替代方案

在 MATLAB 中保存图像的更快替代方案? [复制]