W jakich przypadkach powinienem używać memcpy zamiast standardowych operatorów w C++?

Kiedy Mogę uzyskać lepszą wydajność za pomocą memcpy lub Jak mogę korzystać z niego? Na przykład:

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

Jest kodem:

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

szybszy niż ten?

a[0] = b[0];
a[1] = b[1];
a[2] = b[2];
Author: Patryk Czachurski, 2010-12-28

7 answers

Wydajność nie powinna być twoim zmartwieniem.
Napisz czysty, możliwy do utrzymania kod.

Martwi mnie to, że tak wiele odpowiedzi wskazuje, że memcpy() jest nieefektywne. Jest to najskuteczniejszy sposób kopiowania bloków pamięci (dla programów C).

Więc napisałem co następuje jako test:

#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();
}

Następnie dla porównania kod daje:

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

To spowodowało: (komentarze dodane ręcznie)

=======   // 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

Dodano wyniki pomiaru czasu dla uruchomienia powyższego wewnątrz pętli 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
 48
Author: Martin York,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-17 18:02:14

Możesz używać memcpy tylko wtedy, gdy kopiowane obiekty nie mają jawnych konstruktorów, tak jak ich członkowie (tzw. POD, "zwykłe stare dane"). Można więc wywołać memcpy dla float, ale źle jest dla, np. std::string.

Ale część pracy została już wykonana za Ciebie: std::copy from {[5] } jest wyspecjalizowana dla typów wbudowanych (i prawdopodobnie dla każdego innego typu POD-zależy od implementacji STL). Tak więc pisanie std::copy(a, a + 3, b) jest tak szybkie (po optymalizacji kompilatora) jak memcpy, ale jest mniej podatne na błędy.

 14
Author: crazylammer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-02 06:35:28

Kompilatory specjalnie optymalizują memcpy wywołania, przynajmniej clang & gcc tak robi. Więc powinieneś go preferować, gdzie tylko możesz.

 10
Author: ismail,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-28 09:00:33

Użyj std::copy(). Jako plik nagłówkowy dla g++ Uwagi:

Ta wbudowana funkcja sprowadza się do wywołania @C memmove, o ile to możliwe.

Prawdopodobnie Visual Studio nie różni się zbytnio. Idź w normalny sposób i zoptymalizuj, gdy wiesz o szyjce butelki. W przypadku prostej kopii kompilator prawdopodobnie już się za Ciebie optymalizuje.

 6
Author: Thanatos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-28 09:11:11

Nie wybieraj przedwczesnych mikro-optymalizacji, takich jak używanie memcpy w ten sposób. Użycie przypisania jest bardziej przejrzyste i mniej podatne na błędy, a każdy porządny kompilator wygeneruje odpowiednio wydajny kod. Jeśli, i tylko wtedy, gdy sprofilowałeś kod i uznałeś, że przypisania są znaczącym wąskim gardłem, możesz rozważyć pewien rodzaj mikro-optymalizacji, ale ogólnie powinieneś zawsze pisać jasny, solidny kod w pierwszej instancji.

 5
Author: Paul R,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-28 09:07:21

Korzyści z memcpy? Prawdopodobnie czytelność. W przeciwnym razie musiałbyś wykonać kilka zadań lub mieć pętlę for do kopiowania, z których żadne nie jest tak proste i jasne jak wykonywanie memcpy (oczywiście, o ile Twoje typy są proste i nie wymagają budowy / niszczenia).

Ponadto, memcpy jest na ogół stosunkowo zoptymalizowane dla konkretnych platform, do tego stopnia, że nie będzie dużo wolniejsze niż proste przypisanie, a nawet może być szybsze.

 4
Author: Jamie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-28 09:04:58

Podobno, jak powiedział Nawaz, wersja przypisania powinna być szybsza na większości platform. To dlatego, że memcpy() będzie kopiować bajt po bajcie, podczas gdy druga wersja może kopiować 4 bajty na raz.

Jak zawsze, zawsze powinieneś profilować aplikacje, aby mieć pewność, że to, czego oczekujesz, będzie wąskim gardłem, pasuje do rzeczywistości.

Edit
To samo dotyczy tablicy dynamicznej. Skoro wspominasz o C++ powinieneś użyć algorytmu std::copy() w tym case.

Edit
Jest to kod wyjściowy dla Windows XP z GCC 4.5.0, skompilowany z flagą-o3:

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

Wykonałem tę funkcję, ponieważ op podał również tablice dynamiczne.

Zestaw wyjściowy jest następujący:

_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
Oczywiście, zakładam, że wszyscy eksperci wiedzą, co to znaczy.

To jest wersja przypisania:

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

Co daje następujący kod:

_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

Który przenosi 4 bajty na raz.

 0
Author: Simone,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-28 10:53:48