Я собираюсь пойти против общей мудрости здесь, которая std::copy
будет иметь небольшую, почти незаметную потерю производительности. Я только что сделал тест и обнаружил, что это не соответствует действительности: я заметил разницу в производительности. Однако победителем стал std::copy
.
Я написал реализацию C ++ SHA-2. В моем тесте я хэшировал 5 строк, используя все четыре версии SHA-2 (224, 256, 384, 512), и зацикливался 300 раз. Я измеряю время, используя Boost.timer. Этого счетчика 300 циклов достаточно, чтобы полностью стабилизировать мои результаты. Я запускал тест по 5 раз каждый, чередуя memcpy
версию и std::copy
версию. Мой код использует преимущества сбора данных как можно большим количеством фрагментов (многие другие реализации работают с char
/ char *
, тогда как я работаю с T
/ T *
(где T
самый большой тип в пользовательской реализации, который имеет правильное поведение переполнения), поэтому быстрый доступ к памяти на Наибольшие типы, которые я могу, имеют решающее значение для производительности моего алгоритма. Вот мои результаты:
Время (в секундах) для завершения запуска тестов SHA-2
std::copy memcpy % increase
6.11 6.29 2.86%
6.09 6.28 3.03%
6.10 6.29 3.02%
6.08 6.27 3.03%
6.08 6.27 3.03%
Общее среднее увеличение скорости std :: copy over memcpy: 2,99%
Мой компилятор - gcc 4.6.3 на Fedora 16 x86_64. Мои флаги оптимизации есть -Ofast -march=native -funsafe-loop-optimizations
.
Код для моих реализаций SHA-2.
Я решил провести тест на моей реализации MD5. Результаты были гораздо менее стабильными, поэтому я решил сделать 10 прогонов. Тем не менее, после моих первых нескольких попыток я получил результаты, которые сильно отличались от одного запуска к другому, поэтому я предполагаю, что происходила какая-то активность ОС. Я решил начать все сначала.
Те же настройки компилятора и флаги. Существует только одна версия MD5, и она быстрее, чем SHA-2, поэтому я сделал 3000 циклов на подобном наборе из 5 тестовых строк.
Вот мои последние 10 результатов:
Время (в секундах) до завершения теста MD5
std::copy memcpy % difference
5.52 5.56 +0.72%
5.56 5.55 -0.18%
5.57 5.53 -0.72%
5.57 5.52 -0.91%
5.56 5.57 +0.18%
5.56 5.57 +0.18%
5.56 5.53 -0.54%
5.53 5.57 +0.72%
5.59 5.57 -0.36%
5.57 5.56 -0.18%
Общее среднее снижение скорости std :: copy over memcpy: 0,11%
Код для моей реализации MD5
Эти результаты показывают, что есть некоторая оптимизация, которую std :: copy использовал в моих тестах SHA-2, которую std::copy
нельзя было использовать в моих тестах MD5. В тестах SHA-2 оба массива были созданы в той же функции, которая вызвала std::copy
/ memcpy
. В моих тестах MD5 один из массивов был передан функции в качестве параметра функции.
Я провел немного больше тестов, чтобы увидеть, что я могу сделать, чтобы сделать std::copy
быстрее снова. Ответ оказался простым: включите оптимизацию времени ссылки. Это мои результаты с включенным LTO (опция -flto в gcc):
Время (в секундах) завершения теста MD5 с параметром -flto
std::copy memcpy % difference
5.54 5.57 +0.54%
5.50 5.53 +0.54%
5.54 5.58 +0.72%
5.50 5.57 +1.26%
5.54 5.58 +0.72%
5.54 5.57 +0.54%
5.54 5.56 +0.36%
5.54 5.58 +0.72%
5.51 5.58 +1.25%
5.54 5.57 +0.54%
Общее среднее увеличение скорости std :: copy over memcpy: 0.72%
Таким образом, за использование, похоже, не снижается производительность std::copy
. На самом деле, похоже, увеличение производительности.
Объяснение результатов
Так почему же это может std::copy
повысить производительность?
Во-первых, я не ожидал бы, что это будет медленнее для любой реализации, если включена оптимизация встраивания. Все компиляторы встраиваются агрессивно; это, возможно, самая важная оптимизация, поскольку она позволяет выполнять множество других оптимизаций. std::copy
может (и я подозреваю, что все реализации реального мира) обнаруживают, что аргументы легко копируются и что память распределяется последовательно. Это означает, что в худшем случае, когда memcpy
это законно, std::copy
должно работать не хуже. Тривиальная реализация, от std::copy
которой зависит, memcpy
должна соответствовать критериям вашего компилятора «всегда вставляйте это при оптимизации для скорости или размера».
Тем не менее, std::copy
также хранит больше своей информации. При вызове std::copy
функция сохраняет типы без изменений. memcpy
оперирует void *
, что отбрасывает практически всю полезную информацию. Например, если я передам массив std::uint64_t
, компилятор или разработчик библиотеки могут воспользоваться преимуществами 64-разрядного выравнивания std::copy
, но это может оказаться более сложным memcpy
. Многие реализации алгоритмов, как эта, работают, сначала работая с невыровненной частью в начале диапазона, затем с выровненной частью, затем с невыровненной частью в конце. Если все это гарантированно выровнено, то код становится проще и быстрее, и предиктору ветвления в вашем процессоре становится проще.
Преждевременная оптимизация?
std::copy
находится в интересной позиции. Я ожидаю, что это никогда не будет медленнее, memcpy
а иногда и быстрее, с любым современным оптимизирующим компилятором. Более того, все, что вы можете memcpy
, вы можете std::copy
. memcpy
не допускает никакого перекрытия в буферах, тогда как std::copy
поддерживает перекрытие в одном направлении (с std::copy_backward
другим направлением перекрытия). memcpy
работает только на указатели, std::copy
работает на любых итераторы ( std::map
, std::vector
, std::deque
, или мой собственный пользовательский тип). Другими словами, вы должны просто использовать, std::copy
когда вам нужно скопировать куски данных вокруг.
char
может быть подписанным или неподписанным, в зависимости от реализации. Если число байтов может быть> = 128, то используйтеunsigned char
для своих байтовых массивов. ((int *)
Актерский состав будет также более безопасным(unsigned int *)
.)