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];
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
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.
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.
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.
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.
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.
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.
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