В каких случаях следует использовать memcpy вместо стандартных операторов C ++?

Когда я смогу повысить производительность с помощью memcpy или чем я могу воспользоваться? Например:

float a[3]; float b[3];

это код:

memcpy(a, b, 3*sizeof(float));

быстрее, чем этот?

a[0] = b[0];
a[1] = b[1];
a[2] = b[2];

person Patryk Czachurski    schedule 28.12.2010    source источник
comment
Я предполагаю, что даже оператор присваивания для float будет реализован с помощью memcpy. Таким образом, прямое использование memcpy для всего массива будет быстрее   -  person Akhil    schedule 28.12.2010
comment
Я не верю твоему редактированию. Почему второй подход будет быстрее. memcpy () специально разработан для копирования областей памяти из одного места в другое, поэтому он должен быть настолько эффективным, насколько позволяет базовая архитектура. Я готов поспорить, что он будет использовать соответствующую сборку, где это применимо, для копирования блочной памяти.   -  person Martin York    schedule 28.12.2010


Ответы (7)


Эффективность не должна быть вашей заботой.
Пишите чистый поддерживаемый код.

Меня беспокоит, что так много ответов указывают на неэффективность memcpy (). Он разработан как наиболее эффективный способ копирования блоков памяти (для программ на языке C).

Поэтому в качестве теста я написал следующее:

#include <algorithm>

extern float a[3];
extern float b[3];
extern void base();

int main()
{
    base();

#if defined(M1)
    a[0] = b[0];
    a[1] = b[1];
    a[2] = b[2];
#elif defined(M2)
    memcpy(a, b, 3*sizeof(float));    
#elif defined(M3)
    std::copy(&a[0], &a[3], &b[0]);
 #endif

    base();
}

Затем для сравнения код производит:

g++ -O3 -S xr.cpp -o s0.s
g++ -O3 -S xr.cpp -o s1.s -DM1
g++ -O3 -S xr.cpp -o s2.s -DM2
g++ -O3 -S xr.cpp -o s3.s -DM3

echo "=======" >  D
diff s0.s s1.s >> D
echo "=======" >> D
diff s0.s s2.s >> D
echo "=======" >> D
diff s0.s s3.s >> D

Это привело к: (комментарии добавлены вручную)

=======   // Copy by hand
10a11,18
>   movq    _a@GOTPCREL(%rip), %rcx
>   movq    _b@GOTPCREL(%rip), %rdx
>   movl    (%rdx), %eax
>   movl    %eax, (%rcx)
>   movl    4(%rdx), %eax
>   movl    %eax, 4(%rcx)
>   movl    8(%rdx), %eax
>   movl    %eax, 8(%rcx)

=======    // memcpy()
10a11,16
>   movq    _a@GOTPCREL(%rip), %rcx
>   movq    _b@GOTPCREL(%rip), %rdx
>   movq    (%rdx), %rax
>   movq    %rax, (%rcx)
>   movl    8(%rdx), %eax
>   movl    %eax, 8(%rcx)

=======    // std::copy()
10a11,14
>   movq    _a@GOTPCREL(%rip), %rsi
>   movl    $12, %edx
>   movq    _b@GOTPCREL(%rip), %rdi
>   call    _memmove

Добавлены результаты по времени для выполнения вышеуказанного внутри цикла 1000000000.

   g++ -c -O3 -DM1 X.cpp
   g++ -O3 X.o base.o -o m1
   g++ -c -O3 -DM2 X.cpp
   g++ -O3 X.o base.o -o m2
   g++ -c -O3 -DM3 X.cpp
   g++ -O3 X.o base.o -o m3
   time ./m1

   real 0m2.486s
   user 0m2.478s
   sys  0m0.005s
   time ./m2

   real 0m1.859s
   user 0m1.853s
   sys  0m0.004s
   time ./m3

   real 0m1.858s
   user 0m1.851s
   sys  0m0.006s
person Martin York    schedule 28.12.2010
comment
+1. И, поскольку вы не записали очевидный вывод из этого, вызов memcpy выглядит так, как будто он генерирует наиболее эффективный код. - person Jakob Borg; 28.12.2010
comment
Хм. Почему вызов _memmove не встроен? - person Konrad Rudolph; 28.12.2010
comment
Кстати: @Martin: неразумно говорить, что эффективность не должна быть вашей заботой, пишите хороший код. Люди используют C ++ вместо приличного языка именно потому, что им нужна производительность. Это имеет значение. - person Yttrill; 29.12.2010
comment
@Yttrill: И я никогда не видел микрооптимизации, сделанной человеком, которая еще не улучшилась бы компилятором. С другой стороны, написание красивого читаемого кода подразумевает, что вы больше думаете на уровне алгоритма, где человек может превзойти компилятор при оптимизации, потому что компилятор не знает цели. - person Martin York; 24.07.2015
comment
@LokiAstari Вы думаете, что 20 операторов присваивания легче понять, чем один memcpy в структуре? Это может быть более читаемым с помощью memcpy. (И в C / ++ вы, вероятно, и так привыкли.) - person akaltar; 16.08.2015
comment
@akaltar: Я думаю, что std::copy легче читать. Он также самый эффективный (равный memcpy). См. Прилагаемую сборку и, поскольку вы, очевидно, сами не рассчитали время, результаты синхронизации. - person Martin York; 17.08.2015
comment
@akaltar: Также вы упускаете из виду суть. Вы НЕ МОЖЕТЕ использовать memcopy для структур в C ++, потому что у них есть конструкторы (есть небольшой подкласс структур, для которых вы можете использовать memcopy, но это не общий случай). Вот почему std::copy лучше, поскольку он будет использовать наиболее эффективную и действенную технику. - person Martin York; 17.08.2015
comment
@LokiAstari memcpy обычно используется в структурах только для данных, как обычно, они используются в высокопроизводительном коде, но я понимаю вашу точку зрения, std :: copy действительно лучше. Что касается чтения ... Думаю, они равны. - person akaltar; 18.08.2015
comment
Добавление: вместо массивов в стиле C использование std::array<float, 3>, которое имеет имеет оператор присваивания, объединяет лучшее из обоих миров: удобочитаемость и эффективность. И, помимо прочего, у него есть дополнительное качество, заключающееся в том, что он не распадается на указатель. Кроме того, на момент написания и GCC 5.2, и Clang 3.7 генерируют идентичный код во всех случаях, поэтому производительность больше не актуальна, и следует отдавать предпочтение удобочитаемости. - person user703016; 02.09.2015
comment
Скорость перемещения памяти C ++ (m3) выглядит сомнительной. Нет никакого способа, которым вызов memmove не имел бы накладных расходов по сравнению с оптимизированным и встроенным memcpy случаем. - person user239558; 27.05.2016
comment
@ user239558: Вы удивлены, что std::copy производит самый быстрый код? Учитывая способность компилятора анализировать и внедрять лучший код, кажется избыточно очевидным, что std::copy будет самым быстрым методом (или, по крайней мере, не медленнее, чем метод, который вы можете использовать вручную). - person Martin York; 27.05.2016
comment
@LokiAstari сборка была процитирована в ответе выше. Невозможно не встроить вызов memmove, который в дополнение к вышеупомянутому требованию проверки на перекрытие указателей, мог бы когда-либо быть таким же быстрым, как встроенный memcpy. Это подделка. - person user239558; 30.05.2016
comment
@ user239558: Что ж, компилятор - хорошая работа, поскольку машина делает лучший выбор на основе математики, чем люди, опираясь на мнения. Предоставляется не только код, но и результаты по времени. Поскольку это наука, почему бы вам не повторить эксперимент! - person Martin York; 30.05.2016
comment
@LokiAstari Я просто указываю, что тайминги вымышленные. Это знает любой, кто программировал на сборке или читал инструкции Intel. Ошибки при тестировании - обычное дело, и я просто указываю на очевидную. Я больше не трачу на это время, извини. - person user239558; 30.05.2016
comment
@ user239558: Слова - это просто (а не просто понтификат без доказательств. Просто попробуйте). - person Martin York; 30.05.2016

Вы можете использовать memcpy только в том случае, если копируемые объекты не имеют явных конструкторов, как и их члены (так называемые POD, «Обычные старые данные»). Так что можно вызывать memcpy для float, но нельзя, например, для std::string.

Но часть работы уже сделана за вас: std::copy из <algorithm> специализируется на встроенных типах (и, возможно, для любого другого типа POD - зависит от реализации STL). Таким образом, запись std::copy(a, a + 3, b) выполняется так же быстро (после оптимизации компилятора), как memcpy, но менее подвержена ошибкам.

person crazylammer    schedule 28.12.2010
comment
std::copy правильно находится в <algorithm>; <algorithm.h> строго для обратной совместимости. - person Karl Knechtel; 28.12.2010

Компиляторы специально оптимизируют memcpy вызовы, по крайней мере, clang & gcc. Так что вы должны предпочесть его везде, где можете.

person ismail    schedule 28.12.2010
comment
@ismail: компиляторы могут оптимизировать memcpy, но вряд ли это будет быстрее, чем второй подход. Пожалуйста, прочтите сообщение Симоны. - person Nawaz; 28.12.2010
comment
@Nawaz: Я не согласен. Memcpy (), вероятно, будет быстрее при поддержке архитектуры. В любом случае это избыточно, поскольку std :: copy (как описано @crazylammer), вероятно, является лучшим решением. - person Martin York; 28.12.2010

Не делайте преждевременных микрооптимизаций, таких как использование memcpy, подобного этому. Использование присваивания более четкое и менее подверженное ошибкам, и любой достойный компилятор сгенерирует достаточно эффективный код. Если и только если вы профилировали код и обнаружили, что назначения являются значительным узким местом, тогда вы можете рассмотреть возможность какой-то микрооптимизации, но в целом вы всегда должны писать четкий и надежный код в первую очередь.

person Paul R    schedule 28.12.2010
comment
Как назначение N (где N ›2) разных элементов массива один за другим яснее, чем один memcpy? memcpy(a, b, sizeof a) яснее, потому что при изменении размера a и b вам не нужно добавлять / удалять назначения. - person Chris Lutz; 28.12.2010
comment
@Chris Lutz: вы должны думать о надежности кода на протяжении всей его жизни, например что произойдет, если в какой-то момент кто-то изменит объявление a так, что оно станет указателем вместо массива? В этом случае присваивание не нарушится, а вот memcpy будет. - person Paul R; 28.12.2010
comment
memcpy не сломается (трюк sizeof a сломается, но только некоторые люди его используют). Точно так же и std::copy, который явно превосходит их почти во всех отношениях. - person Chris Lutz; 28.12.2010
comment
@Chris: ну, я бы предпочел цикл for, чем отдельные назначения, и, конечно, осторожное использование memcpy не запрещено для кода C (хотя я бы предпочел не видеть его в коде C ++). Но если вы работаете над кодом, имеющим длительный жизненный цикл, или если вас волнуют такие вещи, как переносимость, перенос на другие языки или компиляторы, использование инструментов анализа кода, автоматическая векторизация и т. Д., Тогда простота и ясность всегда важнее. чем краткость и низкоуровневые хаки. - person Paul R; 28.12.2010

Используйте 1_. В заголовочном файле для g++ примечания:

Эта встроенная функция по возможности сводится к вызову @c memmove.

Наверное, Visual Studio не сильно отличается. Идите обычным путем и оптимизируйте, как только вы заметите бутылочное горлышко. В случае простой копии компилятор, вероятно, уже оптимизирует для вас.

person Thanatos    schedule 28.12.2010

Преимущества memcpy? Наверное читабельность. В противном случае вам пришлось бы либо выполнить несколько назначений, либо создать цикл for для копирования, ни одно из которых не является таким простым и понятным, как просто выполнение memcpy (конечно, если ваши типы просты и не требуют построения / разрушение).

Кроме того, memcpy обычно относительно оптимизирован для конкретных платформ до такой степени, что он не будет намного медленнее, чем простое присваивание, а может даже быть быстрее.

person Jamie    schedule 28.12.2010

Предположительно, как сказал Наваз, версия с назначением должна быть быстрее на большинстве платформ. Это потому, что memcpy() будет копировать байт за байтом, в то время как вторая версия может копировать 4 байта за раз.

Как всегда, вы всегда должны профилировать приложения, чтобы быть уверенным, что то, что вы ожидаете стать узким местом, соответствует действительности.

Изменить
То же самое относится и к динамическому массиву. Поскольку вы упомянули C ++, в этом случае вам следует использовать алгоритм std::copy().

Изменить
Это выходной код для Windows XP с GCC 4.5.0, скомпилированный с флагом -O3:

extern "C" void cpy(float* d, float* s, size_t n)
{
    memcpy(d, s, sizeof(float)*n);
}

Я выполнил эту функцию, потому что OP также указал динамические массивы.

Выходная сборка следующая:

_cpy:
LFB393:
    pushl   %ebp
LCFI0:
    movl    %esp, %ebp
LCFI1:
    pushl   %edi
LCFI2:
    pushl   %esi
LCFI3:
    movl    8(%ebp), %eax
    movl    12(%ebp), %esi
    movl    16(%ebp), %ecx
    sall    $2, %ecx
    movl    %eax, %edi
    rep movsb
    popl    %esi
LCFI4:
    popl    %edi
LCFI5:
    leave
LCFI6:
    ret

конечно, я предполагаю, что все эксперты здесь знают, что означает rep movsb.

Это версия задания:

extern "C" void cpy2(float* d, float* s, size_t n)
{
    while (n > 0) {
        d[n] = s[n];
        n--;
    }
}

что дает следующий код:

_cpy2:
LFB394:
    pushl   %ebp
LCFI7:
    movl    %esp, %ebp
LCFI8:
    pushl   %ebx
LCFI9:
    movl    8(%ebp), %ebx
    movl    12(%ebp), %ecx
    movl    16(%ebp), %eax
    testl   %eax, %eax
    je  L2
    .p2align 2,,3
L5:
    movl    (%ecx,%eax,4), %edx
    movl    %edx, (%ebx,%eax,4)
    decl    %eax
    jne L5
L2:
    popl    %ebx
LCFI10:
    leave
LCFI11:
    ret

Которая перемещает 4 байта за раз.

person Simone    schedule 28.12.2010
comment
@Simone: мне кажется, что первый пункт имеет смысл. Теперь мне нужно это проверить, потому что я не уверен. :-) - person Nawaz; 28.12.2010
comment
Я не думаю, что memcopy копирует байт за байтом. Он специально разработан для очень эффективного копирования больших фрагментов памяти. - person Martin York; 28.12.2010
comment
Источник пожалуйста? Единственное, что требует POSIX, - это this. Кстати, посмотрите, настолько ли быстро эта реализация. - person Simone; 28.12.2010
comment
@Simone - разработчики libc потратили много времени на то, чтобы убедиться, что их memcpy реализации эффективны, а разработчики компиляторов потратили столько же времени на то, чтобы их компиляторы искали случаи, когда memcpy мог бы сделать назначения быстрее, и наоборот. Ваш аргумент может быть настолько плохим, насколько вы этого хотите, а также ваша необычная реализация - отвлекающий маневр. Посмотрите, как это реализовано в GCC или других компиляторах / libc. Возможно, для вас этого будет достаточно быстро. - person Chris Lutz; 28.12.2010
comment
Применяется обычное эмпирическое правило: предполагать, что у авторов библиотеки не поврежден мозг. Зачем им писать memcpy, который может копировать только байт за раз? - person jalf; 28.12.2010
comment
Потому что memcpy требуется для копирования одного байта. Конечно, он может проверять, кратен ли размер для копирования 4 или 8, но с назначениями вы можете пропустить проверку и получить более быстрый код. - person Simone; 28.12.2010
comment
@Simone - на большинстве современных платформ компилятор оптимизирует эту проверку. Большинство компиляторов оптимизируют каждый отдельный вызов memcpy, когда это возможно. - person Chris Lutz; 28.12.2010
comment
@Chris, как видите, это не во всех случаях. - person Simone; 28.12.2010
comment
@Simone: одна вещь: использование цикла while для выполнения назначений и выполнение назначения вручную с использованием постоянного смещения НЕ одинаково по скорости. Последнее обычно быстрее. - person Nawaz; 28.12.2010
comment
Да, но вы не можете назначить вручную, если у вас есть динамически выделяемый массив. - person Simone; 28.12.2010