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

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

Когда я могу получить более высокую производительность с помощью 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];
4b9b3361

Ответ 1

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

Меня беспокоит, что многие ответы указывают на то, что 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    [email protected](%rip), %rcx
>   movq    [email protected](%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    [email protected](%rip), %rcx
>   movq    [email protected](%rip), %rdx
>   movq    (%rdx), %rax
>   movq    %rax, (%rcx)
>   movl    8(%rdx), %eax
>   movl    %eax, 8(%rcx)

=======    // std::copy()
10a11,14
>   movq    [email protected](%rip), %rsi
>   movl    $12, %edx
>   movq    [email protected](%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

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

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

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

Ответ 5

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

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

Ответ 6

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

Ответ 7

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

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

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

Edit
Это вывод кода для 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 байта за раз.