std::vector的reserve()和push_back()比resize()和数组下标更快,为什么?

48

我正在对一段代码进行快速性能测试

void ConvertToFloat( const std::vector< short >& audioBlock, 
                     std::vector< float >& out )
{
    const float rcpShortMax = 1.0f / (float)SHRT_MAX;
    out.resize( audioBlock.size() );
    for( size_t i = 0; i < audioBlock.size(); i++ )
    {
        out[i]  = (float)audioBlock[i] * rcpShortMax;
    }
}

我对比起最初的非常朴素的实现,这个加速后的版本让我很满意,处理65536个音频样本只需要略超过1毫秒。

然而,只是为了好玩,我尝试了以下内容:

void ConvertToFloat( const std::vector< short >& audioBlock, 
                     std::vector< float >& out )
{
    const float rcpShortMax = 1.0f / (float)SHRT_MAX;
    out.reserve( audioBlock.size() );
    for( size_t i = 0; i < audioBlock.size(); i++ )
    {
        out.push_back( (float)audioBlock[i] * rcpShortMax );
    }
}

我本以为这将会和原始代码一样的效率,但突然循环现在只需要900微秒(即比另一个实现快了100微秒)。

有人能解释一下为什么这样可以更高效吗?resize()是否初始化了新分配的向量,而reserve只是分配而不构建?这是我唯一能想到的。

PS,这是在单核2Ghz AMD Turion 64 ML-37上进行测试的。


你在编译代码时,是使用发布版而不是调试版的设置吗? - Laserallan
呵呵呵,是的,我使用了Release。这更像是一个帮助编译器来帮助我的问题 :) - Goz
4个回答

72

resize初始化新分配的向量,而reserve仅分配但不构造吗?

是的。


SGI的STL参考文档解释说,resize“在末尾插入或删除元素”,而reserve仅执行内存分配。http://www.sgi.com/tech/stl/Vector.html - user7116
它将使用向量所设置的任何分配器。 - user7116
1
如果你在调整大小/保留调用之后进行基准测试,你就可以看到这是否是原因。 - Laserallan
2
@Eduardo - 这个使用了向量的配置器(通常你不会看到,因为默认的“只是起作用”大部分应用程序)。配置器有一个接口,其中包括一个用于分配原始内存的函数(allocate())和一个用于在该原始内存中就地构造对象的函数(construct()),以及其他功能。allocate() 可能会使用 malloc() 实现,但这并不是必需的。请参阅 Stephan T. Lavavej 关于“Mallocator”的文章,以了解它如何工作:http://blogs.msdn.com/vcblog/archive/2008/08/28/the-mallocator.aspx - Michael Burr
DDJ还有一篇由Matt Austern撰写的关于分配器的好文章:http://www.ddj.com/cpp/184403759 - Michael Burr

6

Resize()

修改容器,使其恰好拥有n个元素,在必要时在末尾插入元素或从末尾删除元素。如果插入任何元素,则它们都是t的副本。如果n > a.size(),则此表达式等同于a.insert(a.end(), n - size(), t)。如果n < a.size(),则它等同于a.erase(a.begin() + n, a.end())

Reserve()

如果n小于或等于capacity(),则此调用不起作用。否则,这是一个请求分配额外内存的操作。如果请求成功,则capacity()大于或等于n;否则,capacity()保持不变。无论哪种情况,size()都保持不变。

如果向vector中插入了超过capacity() - size()个元素,则会自动重新分配内存。重新分配不会更改size(),也不会更改vector的任何元素的值。但是,它确实增加了capacity()

手动使用Reserve会引起重新分配内存。使用reserve()的主要原因是效率:如果您知道vector最终必须增长到的容量,则通常一次性分配该内存比依赖于自动重新分配方案更有效。


4

首先代码写入 out[i],这归结为 begin() + i(即加法)。第二段代码使用 push_back,它可能会立即写入到已知指针等效的 end()(即没有加法)。您可以通过使用迭代器而不是整数索引,来使第一段代码的运行速度与第二段代码相同。

编辑:还要澄清其他一些评论:向量包含浮点数,构造浮点数实际上是一个无操作(就像声明“float f;”不会发出代码一样,只是告诉编译器在堆栈上为浮点数保存空间)。因此,我认为一个浮点数向量的resize()reserve()之间的任何性能差异都与构造无关。


5
抱歉,但您的建构观点是不正确的。"float f = 0.0f;" 明显比只写 "float f;" 慢。后者是 nop(无操作码),而前者不是。 - Goz
哦,说得好,我不知道构造浮点数会将其赋值为0。当调整大小时,向量将T()分配给每个元素,即float(),即0。不过,使用迭代器而不是整数索引可能会更快。 - AshleysBrain
“构造一个浮点数并将其赋值为0”不一定是正确的。它会被构造出来,无论如何都可以使用。问题在于它是否被“值初始化”。至于使用迭代器的建议,这是正确的方向,但我建议尽可能使用指针;这样,您可以确保不会从迭代器类中获得任何开销。不过,这只有在真正需要优化代码时才值得做;否则,迭代器通常更容易使用。 - underscore_d

1
out.resize( audioBlock.size() );

由于out的大小(= 0)小于audioBlock.size(),因此会创建并附加额外的元素到out的末尾。这通过调用它们的默认构造函数来创建新元素。

Reserve仅分配内存。


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