Я собираю немного кода, используя следующие настройки в VC ++ 2010: / O2 / Ob2 / Oi / Ot
Однако у меня возникли проблемы с пониманием некоторых частей сгенерированной сборки, я добавил некоторые вопросы в код в качестве комментариев.
Кроме того, какое расстояние предварительной выборки обычно рекомендуется на современном процессоре? Я могу провести тестирование на своем собственном процессоре, но я надеялся на какое-то значение, которое хорошо подойдет для более широкого диапазона процессоров. Может быть, можно использовать динамические расстояния предварительной выборки?
<- EDIT: </p>
Еще одна вещь, которая меня удивляет, это то, что компилятор не чередует в некоторой форме инструкции movdqa и movntdq? Поскольку эти инструкции в некотором смысле асинхронны из моего понимания.
Этот код также предполагает наличие 32-байтовых строк кэша при предварительной выборке, однако кажется, что у высокопроизводительных процессоров есть 64-байтовые строки кэширования, поэтому 2 из предварительных выборок, вероятно, могут быть удалены.
->
void memcpy_aligned_x86(void* dest, const void* source, size_t size)
{
0052AC20 push ebp
0052AC21 mov ebp,esp
const __m128i* source_128 = reinterpret_cast<const __m128i*>(source);
for(size_t n = 0; n < size/16; n += 8)
0052AC23 mov edx,dword ptr [size]
0052AC26 mov ecx,dword ptr [dest]
0052AC29 mov eax,dword ptr [source]
0052AC2C shr edx,4
0052AC2F test edx,edx
0052AC31 je copy+9Eh (52ACBEh)
__m128i xmm0 = _mm_setzero_si128();
__m128i xmm1 = _mm_setzero_si128();
__m128i xmm2 = _mm_setzero_si128();
__m128i xmm3 = _mm_setzero_si128();
__m128i xmm4 = _mm_setzero_si128();
__m128i xmm5 = _mm_setzero_si128();
__m128i xmm6 = _mm_setzero_si128();
__m128i xmm7 = _mm_setzero_si128();
__m128i* dest_128 = reinterpret_cast<__m128i*>(dest);
0052AC37 push esi
0052AC38 push edi
0052AC39 lea edi,[edx-1]
0052AC3C shr edi,3
0052AC3F inc edi
{
_mm_prefetch(reinterpret_cast<const char*>(source_128+8), _MM_HINT_NTA);
_mm_prefetch(reinterpret_cast<const char*>(source_128+10), _MM_HINT_NTA);
_mm_prefetch(reinterpret_cast<const char*>(source_128+12), _MM_HINT_NTA);
_mm_prefetch(reinterpret_cast<const char*>(source_128+14), _MM_HINT_NTA);
xmm0 = _mm_load_si128(source_128++);
xmm1 = _mm_load_si128(source_128++);
xmm2 = _mm_load_si128(source_128++);
xmm3 = _mm_load_si128(source_128++);
xmm4 = _mm_load_si128(source_128++);
xmm5 = _mm_load_si128(source_128++);
xmm6 = _mm_load_si128(source_128++);
xmm7 = _mm_load_si128(source_128++);
0052AC40 movdqa xmm6,xmmword ptr [eax+70h] // 1. Why is this moved before the pretecthes?
0052AC45 prefetchnta [eax+80h]
0052AC4C prefetchnta [eax+0A0h]
0052AC53 prefetchnta [eax+0C0h]
0052AC5A prefetchnta [eax+0E0h]
0052AC61 movdqa xmm0,xmmword ptr [eax+10h]
0052AC66 movdqa xmm1,xmmword ptr [eax+20h]
0052AC6B movdqa xmm2,xmmword ptr [eax+30h]
0052AC70 movdqa xmm3,xmmword ptr [eax+40h]
0052AC75 movdqa xmm4,xmmword ptr [eax+50h]
0052AC7A movdqa xmm5,xmmword ptr [eax+60h]
0052AC7F lea esi,[eax+70h] // 2. What is happening in these 2 lines?
0052AC82 mov edx,eax //
0052AC84 movdqa xmm7,xmmword ptr [edx] // 3. Why edx? and not simply eax?
_mm_stream_si128(dest_128++, xmm0);
0052AC88 mov esi,ecx // 4. Is esi never used?
0052AC8A movntdq xmmword ptr [esi],xmm7
_mm_stream_si128(dest_128++, xmm1);
0052AC8E movntdq xmmword ptr [ecx+10h],xmm0
_mm_stream_si128(dest_128++, xmm2);
0052AC93 movntdq xmmword ptr [ecx+20h],xmm1
_mm_stream_si128(dest_128++, xmm3);
0052AC98 movntdq xmmword ptr [ecx+30h],xmm2
_mm_stream_si128(dest_128++, xmm4);
0052AC9D movntdq xmmword ptr [ecx+40h],xmm3
_mm_stream_si128(dest_128++, xmm5);
0052ACA2 movntdq xmmword ptr [ecx+50h],xmm4
_mm_stream_si128(dest_128++, xmm6);
0052ACA7 movntdq xmmword ptr [ecx+60h],xmm5
_mm_stream_si128(dest_128++, xmm7);
0052ACAC lea edx,[ecx+70h]
0052ACAF sub eax,0FFFFFF80h
0052ACB2 sub ecx,0FFFFFF80h
0052ACB5 dec edi
0052ACB6 movntdq xmmword ptr [edx],xmm6 // 5. Why not simply ecx?
0052ACBA jne copy+20h (52AC40h)
0052ACBC pop edi
0052ACBD pop esi
}
}
оригинальный код:
void memcpy_aligned_x86(void* dest, const void* source, size_t size)
{
assert(dest != nullptr);
assert(source != nullptr);
assert(source != dest);
assert(size % 128 == 0);
__m128i xmm0 = _mm_setzero_si128();
__m128i xmm1 = _mm_setzero_si128();
__m128i xmm2 = _mm_setzero_si128();
__m128i xmm3 = _mm_setzero_si128();
__m128i xmm4 = _mm_setzero_si128();
__m128i xmm5 = _mm_setzero_si128();
__m128i xmm6 = _mm_setzero_si128();
__m128i xmm7 = _mm_setzero_si128();
__m128i* dest_128 = reinterpret_cast<__m128i*>(dest);
const __m128i* source_128 = reinterpret_cast<const __m128i*>(source);
for(size_t n = 0; n < size/16; n += 8)
{
_mm_prefetch(reinterpret_cast<const char*>(source_128+8), _MM_HINT_NTA);
_mm_prefetch(reinterpret_cast<const char*>(source_128+10), _MM_HINT_NTA);
_mm_prefetch(reinterpret_cast<const char*>(source_128+12), _MM_HINT_NTA);
_mm_prefetch(reinterpret_cast<const char*>(source_128+14), _MM_HINT_NTA);
xmm0 = _mm_load_si128(source_128++);
xmm1 = _mm_load_si128(source_128++);
xmm2 = _mm_load_si128(source_128++);
xmm3 = _mm_load_si128(source_128++);
xmm4 = _mm_load_si128(source_128++);
xmm5 = _mm_load_si128(source_128++);
xmm6 = _mm_load_si128(source_128++);
xmm7 = _mm_load_si128(source_128++);
_mm_stream_si128(dest_128++, xmm0);
_mm_stream_si128(dest_128++, xmm1);
_mm_stream_si128(dest_128++, xmm2);
_mm_stream_si128(dest_128++, xmm3);
_mm_stream_si128(dest_128++, xmm4);
_mm_stream_si128(dest_128++, xmm5);
_mm_stream_si128(dest_128++, xmm6);
_mm_stream_si128(dest_128++, xmm7);
}
}