【问题标题】:std::vector reserve() and push_back() is faster than resize() and array index, why?std::vector reserve() 和 push_back() 比 resize() 和数组索引快,为什么?
【发布时间】:2009-09-22 16:57:52
【问题描述】:

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

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() 是否初始化刚刚分配但不构造的新分配向量?这是我唯一能想到的。

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

【问题讨论】:

  • 例行检查,您在编译代码时使用的是发布而不是调试设置?
  • 呵呵呵呵,是的,我使用的是 Release。更多的是关于帮助编译器帮助我的问题:)

标签: c++ optimization stl vector


【解决方案1】:

resize 是否会初始化刚刚分配但不构造的新分配的向量?

是的。

【讨论】:

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

调整大小()

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

保留()

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

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

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

【讨论】:

    【解决方案3】:

    第一个代码写入out[i],归结为begin() + i(即添加)。第二个代码使用push_back,它可能立即写入与end() 等效的已知指针(即不添加)。通过使用迭代器而不是整数索引,您可以使第一次运行与第二次运行一样快。

    编辑: 还要澄清一些其他 cmets:向量包含浮点数,构造浮点数实际上是无操作(与声明“float f;”的方式相同,仅告诉编译器为堆栈上的浮点数节省空间)。所以我认为 resize()reserve() 对于浮点向量的任何性能差异都与构造无关。

    【讨论】:

    • 抱歉,您的构造点不正确。浮动 f = 0.0f;显然比“float f;”慢。后者是 nop 前者不是。
    • 哦,公平点,不知道构造一个float分配给它0。Vector在调整大小时将T()分配给每个元素,即float(),它是0。仍然,使用迭代器代替整数索引可能会更快。
    • “构造一个赋值为 0 的浮点数”不一定是真的。无论哪种方式,它都可以构建。否则你永远无法使用它。它是否得到 value-initialised 是这里的问题。至于使用迭代器的建议,正确的轨道,但我会尽可能建议指针;这样,您可以确保不会从迭代器类中获得任何开销。不过,这只值得在真正热门的代码中进行;否则,迭代器通常更容易。
    【解决方案4】:
    out.resize( audioBlock.size() );
    

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

    Reserve 只分配内存。

    【讨论】:

      猜你喜欢
      • 2020-09-22
      • 1970-01-01
      • 2020-06-20
      • 2011-11-15
      • 2021-03-11
      相关资源
      最近更新 更多