2014-05-07 14 views
5

Mam dwa projekty kodu iterującego nad wektorami o rozmiarze 500. Jeden z projektów zawiera tablice 64-bitowych dubletów, a drugi projekt wykorzystuje tablice zawierające 32-bitowe liczby całkowite. Spodziewałem się, że 32-bitowy projekt będzie szybszy, ponieważ więcej przydatnych danych można spakować do pamięci podręcznej.Wektor 64-bitowego podwójnie szybszy do dot-produktu niż wektor 32-bitowego unsigned int?

Kompilator MSVC, most procesora Ivy, kompilacja trybu 64-bitowego.

to kod 1, stosując INTów 32-bitowe (działa w cyklach CPU):

#include <vector> 
#include <iostream> 

int main(){ 

    std::vector<unsigned int> x1; 
    std::vector<unsigned int> x2; 
    std::vector<unsigned int> x3; 
    x1.resize(500); 
    x2.resize(500); 
    x3.resize(500); 

    for(int i =0; i<500; i++){ 
     x1[i] = i; 
     x2[i] = 2*i; 
     x3[i] = 4*i; 
    } 


    int counter = 0; 
    while(counter < 1000){ 
     unsigned long long start = 0; 
     unsigned long long end = 0; 

     double m = 0; 
     double n = 0; 

     start = __rdtsc(); 

     for(int i=0; i < 500; i++){ 
      unsigned int a = x1[i]; 
      unsigned int b = x2[i]; 
      unsigned int g = x3[i]; 
      m = m + (a * g); 
      n = n + (b * g); 
     } 

     end = __rdtscp(); 

     std::cout << (end-start) << "\t\t"<<m << n << std::endl; 
     counter++; 
    } 
} 

wytwarzania tego ASM (os):

start = __rdtscp(&p); 
rdtscp 
lea   r8,[rbp+6Fh] 
mov   dword ptr [r8],ecx 
shl   rdx,20h 
or   rax,rdx 
mov   r10,rax 
     unsigned int p; 
     unsigned int q; 
     unsigned long long start = 0; 
     unsigned long long end = 0; 

     double m = 0; 
mov   r8,rbx 
mov   r9d,1F4h 
      unsigned int a = x1[i]; 
      unsigned int b = x2[i]; 
      unsigned int g = x3[i]; 
mov   edx,dword ptr [r8+r15] 
      m = m + (a * g); 
mov   ecx,edx 
imul  ecx,dword ptr [r8+r14] 
xorps  xmm0,xmm0 
cvtsi2sd xmm0,rcx 
addsd  xmm7,xmm0 
      n = n + (b * g); 
imul  edx,dword ptr [r8] 
mov   eax,edx 
xorps  xmm0,xmm0 
cvtsi2sd xmm0,rax 
addsd  xmm8,xmm0 

     for(int i=0; i < 500; i++){ 
add   r8,4 
dec   r9 
jne   main+0E5h (013F681261h) 
     } 

     end = __rdtscp(&q); 
rdtscp 
     } 

     end = __rdtscp(&q); 
lea   r8,[rbp+6Fh] 
mov   dword ptr [r8],ecx 
shl   rdx,20h 
or   rdx,rax 

to kod 2, używając 64-bitowych podwójnych (kod działa w cykli procesora):

#include <vector> 
#include <iostream> 

int main(){ 

    std::vector<double> x1; 
    std::vector<double> x2; 
    std::vector<unsigned long long> x3; 
    x1.resize(500); 
    x2.resize(500); 
    x3.resize(500); 

    for(int i =0; i<500; i++){ 
     x1[i] = i; 
     x2[i] = 2*i; 
     x3[i] = 4*i; 
    } 

    int counter = 0; 
    while(counter < 1000){ 
     unsigned int p; 
     unsigned int q; 
     unsigned long long start = 0; 
     unsigned long long end = 0; 

     double m = 0; 
     double n = 0; 

     start = __rdtscp(&p); 

     for(int i=0; i < 500; i++){ 
      double a = x1[i]; 
      double b = x2[i]; 
      unsigned long long g = x3[i]; 
      m = m + (a * g); 
      n = n + (b * g); 
     } 

     end = __rdtscp(&q); 

     std::cout << (end-start) << "\t\t"<<m << n << std::endl; 
     counter++; 
    } 
} 

i tu jest ASM (-Os) Produkcja:

start = __rdtscp(&p); 
rdtscp 
lea   r8,[rbp+6Fh] 
mov   dword ptr [r8],ecx 
shl   rdx,20h 
or   rax,rdx 
mov   r9,rax 
     unsigned int p; 
     unsigned int q; 
     unsigned long long start = 0; 
     unsigned long long end = 0; 

     double m = 0; 
mov   rdx,rbx 
mov   r8d,1F4h 
      double a = x1[i]; 
      double b = x2[i]; 
      unsigned long long g = x3[i]; 
mov   rcx,qword ptr [rdx+r15] 
xorps  xmm1,xmm1 
      m = m + (a * g); 
cvtsi2sd xmm1,rcx 
test  rcx,rcx 
jns   main+120h (013F32129Ch) 
addsd  xmm1,xmm9 
movaps  xmm0,xmm1 
mulsd  xmm0,mmword ptr [rdx+r14] 
addsd  xmm6,xmm0 
      n = n + (b * g); 
mulsd  xmm1,mmword ptr [rdx] 
addsd  xmm7,xmm1 

     for(int i=0; i < 500; i++){ 
add   rdx,8 
dec   r8 
jne   main+10Ah (013F321286h) 
     } 

     end = __rdtscp(&q); 
rdtscp 
     } 

     end = __rdtscp(&q); 
lea   r8,[rbp+6Fh] 
mov   dword ptr [r8],ecx 
shl   rdx,20h 
or   rdx,rax 
+0

To procesor 64-bitowy, prawda? –

+1

Operacje zmiennoprzecinkowe mogą być wykonywane równolegle z innymi instrukcjami procesora, co może wyjaśniać rozbieżności. Mimo, że demontaż pokazuje obydwa rejestry xmm, więc teraz jestem zdezorientowany. –

+0

Podobne pytanie z innego forum z pewnymi porównaniami. [zmiennoprzecinkowa vs liczba całkowita] (http://stackoverflow.com/questions/2550281/floating-point-vs-integer-calculations-on-modern-hardware). Z tego co rozumiem, rozszerzone precyzyjne procedury matematyczne, takie jak APFLOAT, wykonują swoje operacje za pomocą zmiennoprzecinkowego, ale nie wiem, czy są używane instrukcje typu SSE. – rcgldr

Odpowiedz

6

Różnica jest przetwarzanie liczb do podwójnej w pierwszym kodem (wektory zawierają unsigned int produkt w całkowitej arytmetycznych, ale zastosowania akumulacji double, w asemblerze dodaje to do kodu instrukcję cvtsi2sd).

W drugim kodzie używasz podwójnych wszędzie, więc nie masz konwersji, a kod działa szybciej.

Ta różnica byłaby znacznie bardziej wyraźna na procesorze, który ma bardziej rygorystyczne rozróżnienie pomiędzy jednostkami przetwarzającymi stałymi i zmiennoprzecinkowymi (na przykład platforma POWER). Platforma X86 jest bardzo wyrozumiała pod tym względem.