假设我必须迭代一个可能非常大的数字向量,并将偶数和奇数元素复制到新的单独向量中。 (源向量可以有任何比例的均值;它可以是所有均衡,所有可能性,或介于两者之间。)
为简单起见,push_back
通常用于此类事情:
for (std::size_t Index; Index < Source.size(); Index++)
{
if (Source[Index] % 2) Odds.push_back(Source[Index]);
else Evens.push_back(Source[Index]);
}
然而,我担心如果它被用作类似排序算法的实现的一部分,这将是低效的并且是有害的,其中性能是最重要的。例如,QuickSort涉及分离元素,就像这样。
您可以使用reserve()
预先分配内存,因此只需要一次分配,但是您必须迭代整个源向量两次 - 一次计算需要整理的元素数量,再一次用于实际复制。
当然,您可以分配与源向量大小相同的空间量,因为新的向量都不需要保留更多,但这似乎有点浪费。
有没有更好的方法让我失踪? push_back()
通常是否可以信任为程序员管理这类事情,还是会对敏感算法造成负担呢?
答案 0 :(得分:9)
我将回答我认为你真正要问的问题,即“在重算法的内循环中应该避免push_back()
吗?”而不是其他人似乎已经阅读了你的帖子,这是“如果我在对大型向量进行无关排序之前调用push_back,这是否重要?”此外,我将从我的经验中回答,而不是花时间追查引文和同行评审的文章。
您的示例基本上是做了两件事,总计CPU成本:它正在读取和操作输入向量中的元素,然后它必须将元素插入到输出向量中。您担心插入元素的成本是因为:
malloc()
is just slow,即使小学生假装new
有所不同)因此,你的直觉是正确的:总是在可能的情况下为你的向量预留空间,不是因为push_back很慢,而是因为它可以触发 慢的重新分配。另外,如果你看一下shrink_to_fit
的实现,你会发现它也会进行副本重新分配,暂时使你的内存成本加倍并造成进一步的碎片化。
这里的问题是你并不总是确切知道输出向量需要多少空间;通常的反应是使用启发式和自定义分配器。默认情况下,为每个输出向量保留n / 2 + k的输入大小,其中k是某个安全范围。这样你通常通常就有足够的空间用于输出,只要你的输入合理平衡,而push_back可以在极少数情况下重新分配。如果你发现push_back的指数行为浪费了太多的内存(当你真正只需要n + 2时你就会保留2n个元素),你可以给它一个自定义的分配器,以较小的线性块扩展矢量大小 - 当然如果向量真的不平衡并且你最终做了很多调整,那么速度会慢得多。
如果事先没有走过输入元件,就无法始终保留足够的空间;但如果您知道通常的余额是什么样的,那么您可以使用启发式方法对其进行很好的猜测,以便在多次迭代中获得统计性能提升。
答案 1 :(得分:2)
当然,您可以分配与源相同的空间量 向量的大小,因为新的向量都不需要超过 那,但这似乎有些浪费。
然后通过致电shrink_to_fit
但是,我担心这会导致效率低下并且会对事物造成伤害 喜欢排序算法。 ... push_back()通常被信任管理 对于程序员来说,这种事情,或者它会变得很麻烦 敏感算法?
是的,push_back是值得信赖的。老实说,我不明白你的顾虑是什么。据推测,如果你在向量上使用算法,你已经将元素放入向量中。你在谈论什么样的算法如何向量元素到达那里,是push_back
还是别的什么?
答案 2 :(得分:2)
如何使用自定义谓词对原始向量进行排序,将所有平均值置于所有赔率之前?
bool EvenBeforeOdd(int a, int b)
{
if ((a - b) % 2 == 0) return a < b;
return a % 2 == 0;
}
std::sort(v.begin(), v.end(), EvenBeforeOdd);
然后你只需要找到最大的偶数,你可以做到,例如使用upper_bound
获得非常大的偶数或类似的数字。一旦找到,就可以制作非常便宜的范围副本。
更新:正如@Blastfurnace评论的那样,使用std::partition
而不是sort
效率更高,因为我们实际上并不需要在每个分区中排序的元素:
bool isEven(int a) { return 0 == a % 2; }
std::vector<int>::const_iterator it = std::partition(v.begin(), v.end(), isEven);
std::vector<int> evens, odds;
evens.reserve(std::distance(v.begin(), it);
odds.reserve(std::distance(it, v.end());
std::copy(v.begin(), it, std::back_inserter(evens));
std::copy(it, v.end(), std::back_inserter(odds));
答案 3 :(得分:1)
如果您的对象是动态创建的,那么向量实际上只是存储指针。这使得向量更加高效,特别是在内部重新分配时。如果多个位置存在相同的对象,这也可以节省内存。
std::vector<YourObject*> Evens;
注意:不要从函数上下文中推送指针,因为这会导致该帧之外的数据损坏。相反,对象需要动态分配。
这可能无法解决您的问题,但可能会有用。
答案 4 :(得分:1)
如果您的子向量正好是一半(奇数/偶数),则只需为每个向量分配50%的原始向量。这样可以避免浪费和shrink_to_fit
。