В каких случаях я должен использовать memcpy поверх стандартных операторов в C ++? - PullRequest
30 голосов
/ 28 декабря 2010

Когда я смогу улучшить производительность, используя 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];

Ответы [ 7 ]

52 голосов
/ 28 декабря 2010

Эффективность не должна вас беспокоить.
Написать чистый обслуживаемый код.

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

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

#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
14 голосов
/ 28 декабря 2010

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

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

10 голосов
/ 28 декабря 2010

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

6 голосов
/ 28 декабря 2010

Используйте std::copy().В качестве заголовочного файла для g++ примечания:

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

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

5 голосов
/ 28 декабря 2010

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

4 голосов
/ 28 декабря 2010

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

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

0 голосов
/ 28 декабря 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 байта за раз.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...