2012-01-19 34 views
44

Tôi đã sử dụng nội tại SSE của Intel trong một thời gian khá lâu với hiệu suất hoạt động tốt. Do đó, tôi mong đợi bản chất của AVX để tăng tốc hơn nữa các chương trình của tôi. Điều này, thật không may, không phải là trường hợp cho đến bây giờ. Có lẽ tôi đang làm một sai lầm ngu ngốc, vì vậy tôi sẽ rất biết ơn nếu ai đó có thể giúp tôi ra ngoài.Sử dụng nội tại AVX thay vì SSE không cải thiện tốc độ - tại sao?

Tôi sử dụng Ubuntu 11.10 với g ++ 4.6.1. Tôi đã biên soạn chương trình của mình (xem bên dưới) với

g++ simpleExample.cpp -O3 -march=native -o simpleExample 

Hệ thống thử nghiệm có CPU Intel i7-2600.

Đây là mã minh họa cho vấn đề của tôi. Trên hệ thống của tôi, tôi nhận được đầu ra

98.715 ms, b[42] = 0.900038 // Naive 
24.457 ms, b[42] = 0.900038 // SSE 
24.646 ms, b[42] = 0.900038 // AVX 

Lưu ý rằng tính toán sqrt (sqrt (sqrt (x))) chỉ được chọn để đảm bảo băng thông bộ nhớ không giới hạn tốc độ thực thi; nó chỉ là một ví dụ.

simpleExample.cpp:

#include <immintrin.h> 
#include <iostream> 
#include <math.h> 
#include <sys/time.h> 

using namespace std; 

// ----------------------------------------------------------------------------- 
// This function returns the current time, expressed as seconds since the Epoch 
// ----------------------------------------------------------------------------- 
double getCurrentTime(){ 
    struct timeval curr; 
    struct timezone tz; 
    gettimeofday(&curr, &tz); 
    double tmp = static_cast<double>(curr.tv_sec) * static_cast<double>(1000000) 
      + static_cast<double>(curr.tv_usec); 
    return tmp*1e-6; 
} 

// ----------------------------------------------------------------------------- 
// Main routine 
// ----------------------------------------------------------------------------- 
int main() { 

    srand48(0);   // seed PRNG 
    double e,s;   // timestamp variables 
    float *a, *b;   // data pointers 
    float *pA,*pB;   // work pointer 
    __m128 rA,rB;   // variables for SSE 
    __m256 rA_AVX, rB_AVX; // variables for AVX 

    // define vector size 
    const int vector_size = 10000000; 

    // allocate memory 
    a = (float*) _mm_malloc (vector_size*sizeof(float),32); 
    b = (float*) _mm_malloc (vector_size*sizeof(float),32); 

    // initialize vectors // 
    for(int i=0;i<vector_size;i++) { 
    a[i]=fabs(drand48()); 
    b[i]=0.0f; 
    } 

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
// Naive implementation 
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
    s = getCurrentTime(); 
    for (int i=0; i<vector_size; i++){ 
    b[i] = sqrtf(sqrtf(sqrtf(a[i]))); 
    } 
    e = getCurrentTime(); 
    cout << (e-s)*1000 << " ms" << ", b[42] = " << b[42] << endl; 

// ----------------------------------------------------------------------------- 
    for(int i=0;i<vector_size;i++) { 
    b[i]=0.0f; 
    } 
// ----------------------------------------------------------------------------- 

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
// SSE2 implementation 
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
    pA = a; pB = b; 

    s = getCurrentTime(); 
    for (int i=0; i<vector_size; i+=4){ 
    rA = _mm_load_ps(pA); 
    rB = _mm_sqrt_ps(_mm_sqrt_ps(_mm_sqrt_ps(rA))); 
    _mm_store_ps(pB,rB); 
    pA += 4; 
    pB += 4; 
    } 
    e = getCurrentTime(); 
    cout << (e-s)*1000 << " ms" << ", b[42] = " << b[42] << endl; 

// ----------------------------------------------------------------------------- 
    for(int i=0;i<vector_size;i++) { 
    b[i]=0.0f; 
    } 
// ----------------------------------------------------------------------------- 

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
// AVX implementation 
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
    pA = a; pB = b; 

    s = getCurrentTime(); 
    for (int i=0; i<vector_size; i+=8){ 
    rA_AVX = _mm256_load_ps(pA); 
    rB_AVX = _mm256_sqrt_ps(_mm256_sqrt_ps(_mm256_sqrt_ps(rA_AVX))); 
    _mm256_store_ps(pB,rB_AVX); 
    pA += 8; 
    pB += 8; 
    } 
    e = getCurrentTime(); 
    cout << (e-s)*1000 << " ms" << ", b[42] = " << b[42] << endl; 

    _mm_free(a); 
    _mm_free(b); 

    return 0; 
} 

Any help is appreciated!

Trả lời

5

Tùy thuộc vào phần cứng bộ xử lý của bạn, hướng dẫn AVX có thể được mô phỏng trong phần cứng dưới dạng hướng dẫn SSE. Bạn cần tìm số bộ phận của bộ vi xử lý để có thông số chính xác, nhưng đây là một trong những điểm khác biệt chính giữa bộ xử lý intel cấp thấp và cao cấp, số lượng đơn vị thực thi chuyên so với giả lập phần cứng.

+0

Tôi đã không biết rằng AVX đã từng mô phỏng - bạn có một tài liệu tham khảo cho việc này? CPU nào sẽ là trường hợp này? –

+19

Trên Sandy Bridge, theo [bảng hướng dẫn] (http://www.agner.org/optimize/instruction_tables.pdf), trang 87--88, có vẻ như 'VDIVPS/PD' thực hiện 2 vi lệnh trên cổng 0 , so với 1 microop cho 'DIVPS/PS'. Các lệnh 'SQRT' sẽ giống nhau. Kể từ khi đơn vị phân chia không được pipelined, thực hiện mất 2x dài hơn. Điều này chỉ ra rằng Sandy Bridge thực sự chỉ có 128-bit thực hiện của đơn vị phân chia. –

+0

@Norbert: cảm ơn vì đã làm rõ - Tôi không biết rằng –

42

Điều này là do VSQRTPS (hướng dẫn AVX) thực hiện chính xác gấp đôi chu kỳ là SQRTPS (lệnh SSE) trên bộ xử lý Sandy Bridge. Xem hướng dẫn tối ưu hóa của Agner Fog: instruction tables, trang 88.

Hướng dẫn như căn bậc hai và bộ phận không được hưởng lợi từ AVX. Mặt khác, bổ sung, nhân, vv, làm.

9

Nếu bạn quan tâm trong việc tăng hiệu suất căn bậc hai, thay vì VSQRTPS bạn có thể sử dụng VRSQRTPS và Newton-Raphson công thức:

x0 = vrsqrtps(a) 
x1 = 0.5 * x0 * (3 - (a * x0) * x0) 

VRSQRTPS tự nó không được hưởng lợi từ AVX, nhưng những tính toán khác làm.

Sử dụng nếu 23 bit chính xác là đủ cho bạn.

6

Chỉ để hoàn thành. Việc triển khai Newton-Raphson (NR) cho các hoạt động như phân chia hoặc căn bậc hai sẽ chỉ mang lại lợi ích nếu bạn có một số giới hạn các hoạt động đó trong mã của bạn. Điều này là do nếu bạn sử dụng các phương pháp thay thế này, bạn sẽ tạo ra nhiều áp lực hơn trên các cổng khác như các cổng nhân và bổ sung. Đó là cơ bản lý do tại sao kiến ​​trúc x86 có đơn vị phần cứng đặc biệt để xử lý các hoạt động này thay vì các giải pháp phần mềm thay thế (như NR). Tôi trích dẫn từ Intel 64 and IA-32 Architectures Optimization Reference Manual p.556:

"Trong một số trường hợp, khi hoạt động chia hoặc căn bậc hai là một phần của thuật toán lớn hơn che giấu một số độ trễ của các hoạt động này, xấp xỉ với Newton-Raphson có thể làm chậm quá trình thực thi . "

Vì vậy, hãy cẩn thận khi sử dụng NR trong các thuật toán lớn. Trên thực tế, tôi đã có luận án thạc sĩ của tôi xung quanh thời điểm này và tôi sẽ để lại một liên kết đến nó ở đây để tham khảo trong tương lai, một khi nó được xuất bản.

Ngoài ra đối với những người luôn thắc mắc về thông lượng và thời gian chờ của một số hướng dẫn nhất định, hãy xem IACA. Nó là một công cụ rất hữu ích được cung cấp bởi Intel để phân tích tĩnh hiệu suất thực thi trong các mã.

sửa đây là một liên kết đến luận án cho những ai quan tâm đến thesis

Các vấn đề liên quan