Başlık saçmalık gibi görünebilir ama açıklamama izin ver. Ben şu montaj kodu karşılaştı zaman Geçen gün bir program inceliyordu:Shufps bellek erişiminden daha yavaş mı?
movaps xmm3, xmmword ptr [rbp-30h]
lea rdx, [rdi+1320h]
movaps xmm5, xmm3
movaps xmm6, xmm3
movaps xmm0, xmm3
movss dword ptr [rdx], xmm3
shufps xmm5, xmm3, 55h
shufps xmm6, xmm3, 0AAh
shufps xmm0, xmm3, 0FFh
movaps xmm4, xmm3
movss dword ptr [rdx+4], xmm5
movss dword ptr [rdx+8], xmm6
movss dword ptr [rdx+0Ch], xmm0
mulss xmm4, xmm3
ve dört yüzer [rdx] ile [RBP-30 saat] çoğunlukla sadece kopya gibi görünüyor. Bu shufps
s, sadece (örneğin shufps xmm5, xmm3, 55h
xmm5
ikinci şamandıra ve yerleştirir seçer) xmm3
dört şamandıraların birini seçmek için kullanılır. Bu shufps
aslında (movss xmm0, dword ptr [rbp-30h]
, movss dword ptr [rdx], xmm0
gibi bir şey) bellek erişimi daha hızlıdır çünkü derleyici öyle olduysa beni meraklandırıyor
.
Yani bu iki yaklaşımı karşılaştırmak için bazı testler yazdım ve çoklu bellek erişen her zaman daha yavaşshufps
bulundu. Şimdi düşünüyorum belki de
shufps
'un kullanımının performans ile ilgisi yoktur. Sadece kodları gizlemek için orada olabilir, böylelikle dekomponentler kolayca temiz kod üretemez (IDA pro ile denenmiş ve gerçekten çok karmaşıktır). Muhtemelen derleyici büyük olasılıkla daha akıllı benden daha olduğu gibi herhangi bir pratik programlarında (örneğin
_mm_shuffle_ps
kullanarak) açıkça zaten
shufps
kullanmak asla iken programı derlenmiş derleyici böyle bir kod oluşturulan neden
, hala bilmek istiyorum . Ne daha hızlı ne de daha küçük. Hiç bir anlamı yok.
Neyse ben aşağıda yazdım testler vereceğiz. testte
#include <Windows.h>
#include <iostream>
using namespace std;
__declspec(noinline) DWORD profile_routine(void (*routine)(void *), void *arg, int iterations = 1)
{
DWORD startTime = GetTickCount();
while (iterations--)
{
routine(arg);
}
DWORD timeElapsed = GetTickCount() - startTime;
return timeElapsed;
}
struct Struct
{
float x, y, z, w;
};
__declspec(noinline) Struct shuffle1(float *arr)
{
float x = arr[3];
float y = arr[2];
float z = arr[0];
float w = arr[1];
return {x, y, z, w};
}
#define SS0 (0x00)
#define SS1 (0x55)
#define SS2 (0xAA)
#define SS3 (0xFF)
__declspec(noinline) Struct shuffle2(float *arr)
{
Struct r;
__m128 packed = *reinterpret_cast<__m128 *>(arr);
__m128 x = _mm_shuffle_ps(packed, packed, SS3);
__m128 y = _mm_shuffle_ps(packed, packed, SS2);
__m128 z = _mm_shuffle_ps(packed, packed, SS0);
__m128 w = _mm_shuffle_ps(packed, packed, SS1);
_mm_store_ss(&r.x, x);
_mm_store_ss(&r.y, y);
_mm_store_ss(&r.z, z);
_mm_store_ss(&r.w, w);
return r;
}
void profile_shuffle_r1(void *arg)
{
float *arr = static_cast<float *>(arg);
Struct q = shuffle1(arr);
arr[0] += q.w;
arr[1] += q.z;
arr[2] += q.y;
arr[3] += q.x;
}
void profile_shuffle_r2(void *arg)
{
float *arr = static_cast<float *>(arg);
Struct q = shuffle2(arr);
arr[0] += q.w;
arr[1] += q.z;
arr[2] += q.y;
arr[3] += q.x;
}
int main(int argc, char **argv)
{
int n = argc + 3;
float arr1[4], arr2[4];
for (int i = 0; i < 4; i++)
{
arr1[i] = static_cast<float>(n + i);
arr2[i] = static_cast<float>(n + i);
}
int iterations = 20000000;
DWORD time1 = profile_routine(profile_shuffle_r1, arr1, iterations);
cout << "time1 = " << time1 << endl;
DWORD time2 = profile_routine(profile_shuffle_r2, arr2, iterations);
cout << "time2 = " << time2 << endl;
return 0;
}
yukarıda, iki karıştır yöntemleri
shuffle1
ve aynı şeyi yapmak
shuffle2
var. MSVC -o2 ile derlenmiş, bu aşağıdaki kodu üretir:
shuffle1:
mov eax,dword ptr [rdx+0Ch]
mov dword ptr [rcx],eax
mov eax,dword ptr [rdx+8]
mov dword ptr [rcx+4],eax
mov eax,dword ptr [rdx]
mov dword ptr [rcx+8],eax
mov eax,dword ptr [rdx+4]
mov dword ptr [rcx+0Ch],eax
mov rax,rcx
ret
shuffle2:
movaps xmm2,xmmword ptr [rdx]
mov rax,rcx
movaps xmm0,xmm2
shufps xmm0,xmm2,0FFh
movss dword ptr [rcx],xmm0
movaps xmm0,xmm2
shufps xmm0,xmm2,0AAh
movss dword ptr [rcx+4],xmm0
movss dword ptr [rcx+8],xmm2
shufps xmm2,xmm2,55h
movss dword ptr [rcx+0Ch],xmm2
ret
shuffle1
benim makinede shuffle2
daha her zaman en az% 30 daha hızlıdır. Ben ihbar shuffle2
iki talimatlar bulunur ve shuffle1
aslında eax
yerine xmm0
kullanır yaptım bu yüzden ben biraz önemsiz aritmetik işlemleri eklerseniz, sonuç farklı olacağını düşündük.
__declspec(noinline) Struct shuffle1(float *arr)
{
float x0 = arr[3];
float y0 = arr[2];
float z0 = arr[0];
float w0 = arr[1];
float x = x0 + y0 + z0;
float y = y0 + z0 + w0;
float z = z0 + w0 + x0;
float w = w0 + x0 + y0;
return {x, y, z, w};
}
#define SS0 (0x00)
#define SS1 (0x55)
#define SS2 (0xAA)
#define SS3 (0xFF)
__declspec(noinline) Struct shuffle2(float *arr)
{
Struct r;
__m128 packed = *reinterpret_cast<__m128 *>(arr);
__m128 x0 = _mm_shuffle_ps(packed, packed, SS3);
__m128 y0 = _mm_shuffle_ps(packed, packed, SS2);
__m128 z0 = _mm_shuffle_ps(packed, packed, SS0);
__m128 w0 = _mm_shuffle_ps(packed, packed, SS1);
__m128 yz = _mm_add_ss(y0, z0);
__m128 x = _mm_add_ss(x0, yz);
__m128 y = _mm_add_ss(w0, yz);
__m128 wx = _mm_add_ss(w0, x0);
__m128 z = _mm_add_ss(z0, wx);
__m128 w = _mm_add_ss(y0, wx);
_mm_store_ss(&r.x, x);
_mm_store_ss(&r.y, y);
_mm_store_ss(&r.z, z);
_mm_store_ss(&r.w, w);
return r;
}
ve talimatların aynı sayıda ve her iki xmm kayıtlarını kullanmak gerekir olarak artık montaj biraz daha adil görünüyor:
yüzden şu şekilde değiştirilmiş. ancak önemli değil.shuffle1
hala% 30 daha hızlı! geniş bağlamda olmadan
Pek olası olmasa da, el yapımı bir montaj olabilir. – tambre
@tambre evet Bunu düşündüm ama bunu yapmak için iyi bir neden düşünemiyorum. Bu, muhtemelen yüz milyonlarca kod satırına sahip büyük bir programdan. Karmaşıklığa rağmen programın belirli kısımlarını optimize etmek istiyorlarsa. Neden aslında optimizasyon olduğundan ve tam tersi olmadığından emin değiller? Bu yüzden derleyiciyi suçluyorum :) – MegaStupidMonkeys
Belki de hizalanmış bellek erişimleri eski işlemcilerde çok daha hızlıydı. Bu yüzden derleyici, dört 4 bayt hizalanmamış yük yerine 16 bayt hizalanmış bir yük yapmayı tercih etti. Ayrıca belki de derleyici kayan nokta verileri için 'eax 'gibi yazmaçları kullanamadı. Son olarak, bellek yükü ve shuffle komutlarının hızını karşılaştırmanın akıllıca olmadığını unutmayın. CPU içinde ayrı yürütme birimleri kullandığından, bu iki komut türü paralel olarak çalışabilir. Gerçek performans, burada darboğaz olan şey tarafından tanımlanır ... – stgatilov