Memcpy против Memmove - отладка против выпуска
Я получил очень странное поведение для своего многопоточного приложения x64. Время выполнения в режиме отладки быстрее, чем в режиме выпуска.
Я разбил проблему и обнаружил проблему: режим отладки оптимизирует (! Примечание оптимизация выключена!) Memcpy для memmove, что происходит быстрее. В режиме выпуска по-прежнему используется memcpy (оптимизация! Note включена).
Эта проблема замедляет работу моего многопоточного приложения в режиме выпуска.:(
Кто-нибудь есть идеи?
#include <time.h>
#include <iostream>
#define T_SIZE 1024*1024*2
int main()
{
clock_t start, end;
char data[T_SIZE];
char store[100][T_SIZE];
start = clock();
for (int i = 0; i < 4000; i++) {
memcpy(store[i % 100], data, T_SIZE);
}
// Debug > Release Time 1040 < 1620
printf("memcpy: %d\n", clock() - start);
start = clock();
for (int i = 0; i < 4000; i++) {
memmove(store[i % 100], data, T_SIZE);
}
// Debug > Release Time 1040 > 923
printf("memmove: %d\n", clock() - start);
}
2 ответа
Следующий ответ действителен ТОЛЬКО для VS2013
То, что у нас здесь, на самом деле страннее, чем просто memcpy
против memmove
, Это тот случай, когда внутренняя оптимизация действительно замедляет процесс. Проблема связана с тем, что VS2013 имеет встроенную функцию memcopy следующим образом:
; 73 : memcpy(store[i % 100], data, sizeof(data));
mov eax, 1374389535 ; 51eb851fH
mul esi
shr edx, 5
imul eax, edx, 100 ; 00000064H
mov ecx, esi
sub ecx, eax
movsxd rcx, ecx
shl rcx, 21
add rcx, r14
mov rdx, r13
mov r8d, 16384 ; 00004000H
npad 12
$LL413@wmain:
movups xmm0, XMMWORD PTR [rdx]
movups XMMWORD PTR [rcx], xmm0
movups xmm1, XMMWORD PTR [rdx+16]
movups XMMWORD PTR [rcx+16], xmm1
movups xmm0, XMMWORD PTR [rdx+32]
movups XMMWORD PTR [rcx+32], xmm0
movups xmm1, XMMWORD PTR [rdx+48]
movups XMMWORD PTR [rcx+48], xmm1
movups xmm0, XMMWORD PTR [rdx+64]
movups XMMWORD PTR [rcx+64], xmm0
movups xmm1, XMMWORD PTR [rdx+80]
movups XMMWORD PTR [rcx+80], xmm1
movups xmm0, XMMWORD PTR [rdx+96]
movups XMMWORD PTR [rcx+96], xmm0
lea rcx, QWORD PTR [rcx+128]
movups xmm1, XMMWORD PTR [rdx+112]
movups XMMWORD PTR [rcx-16], xmm1
lea rdx, QWORD PTR [rdx+128]
dec r8
jne SHORT $LL413@wmain
Проблема в том, что мы выполняем загрузку и сохранение SSE без выравнивания, что на самом деле медленнее, чем просто использование стандартного кода C. Я подтвердил это, взяв реализацию CRT из исходного кода, включенного в Visual Studio, и сделав my_memcpy
В качестве способа обеспечения того, чтобы кэш был теплым во время всего этого, я предварительно инициализировал все data
но результаты говорили:
Разогрев занял 43мс
my_memcpy up заняло 862мс
memmove up занял 676мс
memcpy up занял 1329мс
Так почему же memmove
Быстрее? Потому что он не пытается предварительно оптимизировать, потому что он должен предполагать, что данные могут перекрываться.
Для любопытных это мой код полностью:
#include <cstdlib>
#include <cstring>
#include <chrono>
#include <iostream>
#include <random>
#include <functional>
#include <limits>
namespace {
const auto t_size = 1024ULL * 1024ULL * 2ULL;
__declspec(align(16 )) char data[t_size];
__declspec(align(16 )) char store[100][t_size];
void * __cdecl my_memcpy(
void * dst,
const void * src,
size_t count
)
{
void * ret = dst;
/*
* copy from lower addresses to higher addresses
*/
while (count--) {
*(char *)dst = *(char *)src;
dst = (char *)dst + 1;
src = (char *)src + 1;
}
return(ret);
}
}
int wmain(int argc, wchar_t* argv[])
{
using namespace std::chrono;
std::mt19937 rd{ std::random_device()() };
std::uniform_int_distribution<short> dist(std::numeric_limits<char>::min(), std::numeric_limits<char>::max());
auto random = std::bind(dist, rd);
auto start = steady_clock::now();
// warms up the cache and initializes
for (int i = 0; i < t_size; ++i)
data[i] = static_cast<char>(random());
auto stop = steady_clock::now();
std::cout << "Warm up took " << duration_cast<milliseconds>(stop - start).count() << "ms\n";
start = steady_clock::now();
for (int i = 0; i < 4000; ++i)
my_memcpy(store[i % 100], data, sizeof(data));
stop = steady_clock::now();
std::cout << "my_memcpy took " << duration_cast<milliseconds>(stop - start).count() << "ms\n";
start = steady_clock::now();
for (int i = 0; i < 4000; ++i)
memmove(store[i % 100], data, sizeof(data));
stop = steady_clock::now();
std::cout << "memmove took " << duration_cast<milliseconds>(stop - start).count() << "ms\n";
start = steady_clock::now();
for (int i = 0; i < 4000; ++i)
memcpy(store[i % 100], data, sizeof(data));
stop = steady_clock::now();
std::cout << "memcpy took " << duration_cast<milliseconds>(stop - start).count() << "ms\n";
std::cin.ignore();
return 0;
}
Обновить
Во время отладки я обнаружил, что компилятор обнаружил, что код, который я скопировал из CRT, memcpy
, но он связывает его с не встроенной версией в самой CRT, которая использует rep movs
вместо массивной петли SSE выше. Кажется, проблема только с внутренней версией.
Обновление 2
По словам Бозона, в комментариях все зависит от архитектуры. На моем процессоре rep movsb
быстрее, но на старых процессорах реализация SSE или AVX потенциально может быть быстрее. Это в соответствии с Руководством по оптимизации Intel. Для невыровненных данных как rep movsb
может испытывать до 25% штрафа на старом оборудовании. Тем не менее, как представляется, для подавляющего большинства случаев и архитектур rep movsb
в среднем превзойдет реализацию SSE или AVX.
Идея: позвонить memmove
, поскольку это самый быстрый для вашего случая.