我写了一个基准,用于测试将boost :: container :: static_vector修改并推入std :: vector的两种方法。他们似乎表现得很奇怪。
第一个:
NODE_PATH=src
它将修改本地版本,将其复制到引导程序上,然后将本地版本恢复为原始状态。
第二个:
typedef boost::container::static_vector<int, 4> Bar;
void foo(Bar& bar, std::vector<Bar>& bars)
{
const auto oldSize = bar.size();
bar.emplace_back(4);
bars.emplace_back(bar);
bar.resize(oldSize);
}
它无需修改和还原本地版本,而是直接在vector上直接修改其副本。
我的理由是,两个版本中的typedef boost::container::static_vector<int, 4> Bar;
void foo(Bar& bar, std::vector<Bar>& bars)
{
bars.emplace_back(bar);
bars.back().emplace_back(4);
}
都应具有相同的性能,因为它们都只复制固定大小的对象。此外,两个foo实现都只调用一次vector::emplace_back
。 static_vector::emplace_back
似乎很便宜。它应该归结为检查resize
是否小于当前oldSize
的大小(始终是)并覆盖内部bar
的大小变量。显然,这里没有析构函数调用。相反,第二个模拟调用bar
寻址堆内存。
总而言之,我希望两个版本都具有相同的性能,甚至可能在第一个版本方面稍有优势(看到它只处理一次堆内存,与第二个相反)。然而,简单的基准测试则显示了其他结果。
基准:
vector::back
第一:
std::vector<Bar> bars;
for(int i = 0; i < 100; ++i)
{
bars.clear();
Bar bar = {5, 6, 7};
for(int j = 0; j < 10000000; ++j)
foo(bar, bars);
}
第二:
real 0m8,044s
user 0m7,846s
sys 0m0,152s
问题是,这种巨大的差异来自何处?
奇怪的是,更改real 0m5,754s
user 0m5,559s
sys 0m0,184s
的容量似乎可以缩小此差距。例如
static_vector
消除时差。尽管较小的typedef boost::container::static_vector<int, 16> Bar;
容量似乎不受此问题的影响,为什么在这两种方式之间会有如此大的区别?
我使用带有标志static_vector
的g ++ 8.3.0。
编辑:
这里的基准测试时间更长,-Ofast -std=c++17 -flto
容量更大(8):
static_vector
std::vector<Bar> bars;
for(int i = 0; i < 1000; ++i)
{
bars.clear();
Bar bar = {5, 6, 7, 8, 9, 10, 11};
for(int j = 0; j < 10000000; ++j)
foo(bar, bars);
}
与1m42,282s
仍然显示出相似的差异。确实,第一个实现可以摆脱它只是复制更多字节的解释。但是接下来的问题是,为什么容量提升到16的类似基准仅显示1m31,387s
与2m46,733s
。我希望看到相同的11s差异。
答案 0 :(得分:0)
首先,我们必须认识到两种方法实际上并不等效。我们称它们为fooA
和fooB
。
typedef boost::container::static_vector<int, 4> Bar;
void fooA(Bar& bar, std::vector<Bar>& bars)
{
const auto oldSize = bar.size();
bar.emplace_back(4);
bars.emplace_back(bar);
bar.resize(oldSize);
}
void fooB(Bar& bar, std::vector<Bar>& bars)
{
bars.emplace_back(bar);
bars.back().emplace_back(4);
}
fooA
当我们叫fooA
时,会发生以下情况:
bar
4
附加到bar
的末尾。如果失败,则会引发异常,并且该函数将退出,而不会在bars
中添加任何内容。 bar
可能处于无效状态。 bar
被复制到bars
bar
返回其原始状态。 fooB
fooB
在成功案例中具有相同的行为,而在失败案例中不同的行为。
bar
复制到bars
4
的末尾将bars
附加到新向量。如果失败,则抛出异常,函数退出(但bars
的末尾附加了一些内容)。 bar
永远不会处于无效状态,因为它从未更改。 fooA
和fooB
的行为在成功案例中是相同的(无例外),但在引发异常时它们的行为有所不同。 ,编译器不能对它们两者都应用相同的优化,并且不能删除诸如resize
之类的行为。这导致fooB
更快。