Подтвердить что ты не робот

Быстрая копия `std::vector <std:: uint8_t>`

У меня есть std::vector<std::uint8_t>, который нужно дублировать. Это делается просто путем вызова конструктора копирования.

Мои результаты профилирования показывают, что реализация Microsoft Visual С++ (msvc100) использует std::uninitialized_copy внутренне. Это копирует каждый элемент один за другим. В этом случае более оптимизированная копия может быть выполнена путем одновременного копирования целых блоков памяти (например, memcpy).

Другими словами, это может быть значительная оптимизация. Есть ли способ заставить вектор использовать такой оптимизированный метод?

Примечание. Я попытался использовать std::basic_string<std::uint8_t>, и он работает лучше, но у него есть другие проблемы.

4b9b3361

Ответ 1

Основываясь на предлагаемых решениях, я решил составить небольшой ориентир.

#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;
}

На моем ПК, скомпилированном для x64 с msvc100, полностью оптимизирован, он производит следующий вывод:

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

Результаты довольно ясны: std::copy выполняет также и std::memcpy, тогда как оба конструктора и assign на порядок медленнее. Конечно, точные числа и отношения зависят от размера вектора, но вывод для msvc100 очевиден: как предложенный Rapptz, используйте std::copy.

Изменить: вывод не является очевидным для других компиляторов. Я тестировал и в 64-битном Linux, со следующим результатом для Clang 3.2

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

GCC 4.8 дает аналогичный результат. Для GCC в Windows memcpy и copy были немного медленнее конструкторов и assign, хотя разница была меньше. Однако, мой опыт заключается в том, что GCC не очень хорошо оптимизируется в Windows. Я также тестировал msvc110, и результаты были похожи на msvc100.

Ответ 2

Этот ответ не относится к msvc100.

Если вы используете конструктор копирования, как в

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

должен быть скопирован (и использован) объект-распределитель otherVect, который требует больше усилий, чтобы получить его в реализации STL.

Если вы просто хотите скопировать содержимое otherVect, используйте

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

который использует распределитель по умолчанию для newVect.

Другая возможность -

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

Все из них (включая константу копирования, когда otherVect использует распределитель по умолчанию) должны в этом случае сводиться к memmove/memcpy в хорошей реализации STL. Позаботьтесь, что otherVect имеет точно такой же тип элемента (например, "char" или "int8_t" ) как newVect.

Использование метода контейнера, как правило, более эффективно, чем использование общих алгоритмов, поэтому комбинация vector:: resize() и std:: copy() или даже memmove()/memcpy() будет работать, если поставщик недостаточно оптимизировал контейнер.