代码之家  ›  专栏  ›  技术社区  ›  Ruud

快速复制`std::vector<std::uint8_t>`

  •  24
  • Ruud  · 技术社区  · 11 年前

    我有一个 std::vector<std::uint8_t> ,需要进行复制。这只需调用复制构造函数即可完成。

    我的评测结果显示,Microsoft Visual C++(msvc100)实现使用 std::uninitialized_copy 内部。这将逐个复制每个元素。在这种情况下,可以通过一次复制整个内存块(如 memcpy 可以这样做)。

    换句话说,这可能是一个重大的优化。有没有办法强制矢量使用这样一种优化的方法?

    注意:我已经尝试过使用 std::basic_string<std::uint8_t> ,并且它确实表现得更好,但它还有其他问题。

    2 回复  |  直到 11 年前
        1
  •  7
  •   Jacob    11 年前

    这个答案并不是msvc100特有的。

    如果像中那样使用复制构造函数

    std::vector<uint8_t> newVect(otherVect);
    

    otherVect的分配器对象也必须被复制(和使用),这需要付出更多的努力才能使其在STL实现中具有性能。

    如果您只想复制 目录 其他车辆,使用

    std::vector<uint8_t> newVect(otherVect.begin(), otherVect.end());
    

    它使用newVect的默认分配器。

    另一种可能性是

    std::vector<uint8_t> newVect; nevVect.assign(otherVect.begin(), otherVect.end());
    

    在这种情况下,在一个好的STL实现中,所有这些(包括其他Vect使用默认分配器时的复制构造函数)都应该归结为memmove/memcpy。请注意,otherVect与newVect具有完全相同的元素类型(例如“char”或“int8_t”)。

    使用容器的方法通常比使用通用算法更具性能,因此,如果供应商没有充分优化容器,那么vector::resize()和std::copy()甚至memmove()/memcpy()的组合将是一种解决方案。

        2
  •  2
  •   Community Egal    7 年前

    根据建议的解决方案,我决定制定一个小型基准。

    #include <cstdint>
    #include <cstring>
    #include <ctime>
    #include <iostream>
    #include <random>
    #include <vector>
    
    using namespace std;
    
    int main()
    {
      random_device seed;
      mt19937 rnd(seed());
      uniform_int_distribution<uint8_t> random_byte(0x00, 0xff);
    
      const size_t n = 512 * 512;
    
      vector<uint8_t> source;
      source.reserve(n);
      for (size_t i = 0; i < n; i++) source.push_back(random_byte(rnd));
    
      clock_t start;
      clock_t t_constructor1 = 0; uint8_t c_constructor1 = 0;
      clock_t t_constructor2 = 0; uint8_t c_constructor2 = 0;
      clock_t t_assign = 0;       uint8_t c_assign = 0;
      clock_t t_copy = 0;         uint8_t c_copy = 0;
      clock_t t_memcpy = 0;       uint8_t c_memcpy = 0;
    
      for (size_t k = 0; k < 4; k++)
      {
        start = clock();
        for (size_t i = 0; i < n/32; i++)
        {
          vector<uint8_t> destination(source);
          c_constructor1 += destination[i];
        }
        t_constructor1 += clock() - start;
    
        start = clock();
        for (size_t i = 0; i < n/32; i++)
        {
          vector<uint8_t> destination(source.begin(), source.end());
          c_constructor2 += destination[i];
        }
        t_constructor2 += clock() - start;
    
        start = clock();
        for (size_t i = 0; i < n/32; i++)
        {
          vector<uint8_t> destination;
          destination.assign(source.begin(), source.end());
          c_assign += destination[i];
        }
        t_assign += clock() - start;
    
        start = clock();
        for (size_t i = 0; i < n/32; i++)
        {
          vector<uint8_t> destination(source.size());
          copy(source.begin(), source.end(), destination.begin());
          c_copy += destination[i];
        }
        t_copy += clock() - start;
    
        start = clock();
        for (size_t i = 0; i < n/32; i++)
        {
          vector<uint8_t> destination(source.size());
          memcpy(&destination[0], &source[0], n);
          c_memcpy += destination[i];
        }
        t_memcpy += clock() - start;
      }
    
      // Verify that all copies are correct, but also prevent the compiler
      // from optimising away the loops
      uint8_t diff = (c_constructor1 - c_constructor2) +
                     (c_assign - c_copy) +
                     (c_memcpy - c_constructor1);
    
      if (diff != 0) cout << "one of the methods produces invalid copies" << endl;
    
      cout << "constructor (1): "    << t_constructor1 << endl;
      cout << "constructor (2): "    << t_constructor2 << endl;
      cout << "assign:          "    << t_assign << endl;
      cout << "copy             "    << t_copy << endl;
      cout << "memcpy           "    << t_memcpy << endl;
    
      return 0;
    }
    

    在我的电脑上,使用msvc100为x64编译,经过充分优化,这会产生以下输出:

    constructor (1): 22388
    constructor (2): 22333
    assign:          22381
    copy             2142
    memcpy           2146
    

    结果非常清楚: std::copy 表现良好 std::memcpy ,而构造函数和 assign 慢了一个数量级。当然,确切的数字和比率取决于向量大小,但msvc100的结论是显而易见的: suggested by Rapptz 使用 标准::副本 .

    编辑: 这一结论对其他编纂者来说并不明显。我也在64位Linux上进行了测试,Clang 3.2的结果如下

    constructor (1): 530000
    constructor (2): 560000
    assign:          560000
    copy             840000
    memcpy           860000
    

    GCC 4.8给出了类似的输出。对于Windows上的GCC, memcpy copy 比构造函数稍慢 分配 ,尽管差异较小。然而,我的经验是GCC在Windows上的优化效果不是很好。我也测试了msvc110,结果与msvc100相似。