Когда я могу получить более высокую производительность с помощью 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];
Когда я могу получить более высокую производительность с помощью 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];
Эффективность не должна беспокоить вас.
Напишите чистый поддерживаемый код.
Меня беспокоит, что многие ответы указывают на то, что 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
Вы можете использовать memcpy
, только если объекты, которые вы копируете, не имеют явных конструкторов, так как их члены (так называемые POD, "Обычные старые данные" ). Так что нормально называть memcpy
для float
, но это неправильно для, например, std::string
.
Но часть работы уже сделана для вас: std::copy
from <algorithm>
специализирован для встроенных типов (и, возможно, для каждого другого POD-типа - зависит от реализации STL). Поэтому запись std::copy(a, a + 3, b)
выполняется так же быстро (после оптимизации компилятора) как memcpy
, но менее подвержена ошибкам.
Составители специально оптимизируют вызовы memcpy
, по крайней мере, clang и gcc. Поэтому вы должны отдать предпочтение, где бы вы ни были.
Используйте std::copy()
. Как заголовочный файл для g++
отмечает:
Эта встроенная функция будет сводиться к вызову @c memmove, когда это возможно.
Возможно, Visual Studio не сильно отличается. Идите обычным способом и оптимизируйте, как только вы узнаете о бутылочной шее. В случае простой копии компилятор, вероятно, уже оптимизирован для вас.
Преимущества memcpy? Вероятно, читаемость. В противном случае вам придется либо выполнять несколько заданий, либо иметь цикл for для копирования, ни один из которых не является таким простым и понятным, как просто выполнение memcpy (конечно, если ваши типы просты и не требуют построения/разрушение).
Кроме того, memcpy, как правило, относительно оптимизирован для определенных платформ, до такой степени, что он не будет намного медленнее простого назначения и может даже быть быстрее.
Не используйте преждевременные микрооптимизации, такие как использование memcpy. Использование назначения является более четким и менее подверженным ошибкам, и любой достойный компилятор будет генерировать подходящий эффективный код. Если и только если вы профилировали код и обнаружили, что присвоения являются значительным узким местом, вы можете рассмотреть некоторую микро-оптимизацию, но в целом вы всегда должны писать четкий, надежный код в первом экземпляре.
Предположительно, как сказал Наваз, версия назначения должна быть быстрее на большинстве платформ. Это потому, что 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 байта за раз.