Я собираюсь пойти против общей мудрости здесь, которая 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 *).)