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 分别使用 1
和 2
调用 V.push_back()
。从 A 的角度来看,V.size() == 0
,因此它将1
插入位置0
。从 B 的角度来看,由于它与 A 同时执行,V.size() == 0
,所以它将2
插入到位置0
(实际上,它可能更糟,因为可能会写入不同的字节,你可能会得到一个完全不同的数字;取决于编写 float
是否是原子的)。同时,A 增加 V.size
并且 B 也这样做。在此之后,V.size()
可能是1
、2
或其他,而V[0]
也是未知的
【参考方案1】:
如果您知道最终大小,那么reserve() 在您声明向量后的那个大小。这样它只需要分配一次内存。
此外,您可以尝试使用emplace_back(),尽管我怀疑它对于float
的向量会有什么不同。但是尝试它并对其进行基准测试(当然,使用优化的构建 - 你正在使用优化的构建 - 对吗?)。
【讨论】:
push_back
和 emplace_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<float[]>
)的所有便利.但是,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<float[]>
。您可以使用
size_t size = /* Number of floats */;
auto floats = unique_ptr<float[]>new float[size];
你不需要做任何特别的事情来删除它;当它超出范围时,它将释放内存。在大多数情况下,您可以像使用矢量一样使用它,但它不会自动调整大小。
【讨论】:
以上是关于push_back 的更快替代方案(已知大小)的主要内容,如果未能解决你的问题,请参考以下文章