2017-04-06 17 views
5

tôi thử nghiệm các chức năng đơn giản sau đâyAVX hoạt động vô hướng là nhanh hơn nhiều

void mul(double *a, double *b) { 
    for (int i = 0; i<N; i++) a[i] *= b[i]; 
} 

với mảng rất lớn để nó là băng thông bộ nhớ bị ràng buộc. Mã thử nghiệm tôi sử dụng ở bên dưới. Khi tôi biên dịch với -O2 mất 1.7 giây. Khi tôi biên dịch với -O2 -mavx chỉ mất 1.0 giây. Các hoạt động vô hướng mã hóa không được mã hóa vex chậm hơn 70%! Tại sao điều này?

Đây là phiên bản dành cho -O2-O2 -mavx. vimddif of <code>-O2</code> and <code>-O2 -mavx</code>

https://godbolt.org/g/w4p60f

hệ thống: [email protected] (Skylake) 32 GB mem, Ubuntu 16.10, GCC 6.3

mã kiểm tra

//gcc -O2 -fopenmp test.c 
//or 
//gcc -O2 -mavx -fopenmp test.c 
#include <string.h> 
#include <stdio.h> 
#include <x86intrin.h> 
#include <omp.h> 

#define N 1000000 
#define R 1000 

void mul(double *a, double *b) { 
    for (int i = 0; i<N; i++) a[i] *= b[i]; 
} 

int main() { 
    double *a = (double*)_mm_malloc(sizeof *a * N, 32); 
    double *b = (double*)_mm_malloc(sizeof *b * N, 32); 

    //b must be initialized to get the correct bandwidth!!! 
    memset(a, 1, sizeof *a * N); 
    memset(b, 1, sizeof *b * N); 

    double dtime; 
    const double mem = 3*sizeof(double)*N*R/1024/1024/1024; 
    const double maxbw = 34.1; 
    dtime = -omp_get_wtime(); 
    for(int i=0; i<R; i++) mul(a,b); 
    dtime += omp_get_wtime(); 
    printf("time %.2f s, %.1f GB/s, efficency %.1f%%\n", dtime, mem/dtime, 100*mem/dtime/maxbw); 

    _mm_free(a), _mm_free(b); 
} 
+0

FWIW Tôi nhận được khoảng 0,8 cho cả hai trên CPU Haswell 2,6 GHz di động thấp, biên dịch bằng tiếng kêu. –

+0

@PaulR, cảm ơn bạn đã kiểm tra. Tôi có thể kiểm tra nó sau này trên hệ thống Haswell của tôi. Tôi nhận được kết quả lạ trên hệ thống Skylake của tôi mà tôi không nhận được trên Haswell vì vậy tôi sẽ không ngạc nhiên. –

+0

@PaulR, tôi đã tìm ra!'__asm__ __volatile__ (" vzeroupper ":::);' ngay sau khi các cuộc gọi đến 'omp_get_wtime()' sửa chữa nó. –

Trả lời

5

Vấn đề có liên quan đến một nửa trên bẩn của thanh ghi AVX sau khi gọi omp_get_wtime(). Đây là một vấn đề đặc biệt đối với bộ xử lý Skylake.

Lần đầu tiên tôi đọc về sự cố này là here. Kể từ đó, những người khác đã quan sát vấn đề này: herehere.

Sử dụng gdb Tôi thấy rằng omp_get_wtime() gọi clock_gettime. Tôi viết lại mã của mình để sử dụng clock_gettime() và tôi thấy cùng một vấn đề.

void fix_avx() { __asm__ __volatile__ ("vzeroupper" : : :); } 
void fix_sse() { } 
void (*fix)(); 

double get_wtime() { 
    struct timespec time; 
    clock_gettime(CLOCK_MONOTONIC, &time); 
    #ifndef __AVX__ 
    fix(); 
    #endif 
    return time.tv_sec + 1E-9*time.tv_nsec; 
} 

void dispatch() { 
    fix = fix_sse; 
    #if defined(__INTEL_COMPILER) 
    if (_may_i_use_cpu_feature (_FEATURE_AVX)) fix = fix_avx; 
    #else 
    #if defined(__GNUC__) && !defined(__clang__) 
    __builtin_cpu_init(); 
    #endif 
    if(__builtin_cpu_supports("avx")) fix = fix_avx; 
    #endif 
} 

Đẩy mạnh thông qua mã với gdb Tôi thấy rằng lần đầu tiên clock_gettime được gọi là nó gọi _dl_runtime_resolve_avx(). Tôi tin rằng vấn đề là trong chức năng này dựa trên this comment. Chức năng này dường như chỉ được gọi là lần đầu tiên clock_gettime được gọi.

Với GCC vấn đề đi xa sử dụng //__asm__ __volatile__ ("vzeroupper" : : :); sau khi cuộc gọi đầu tiên với clock_gettime tuy nhiên với Clang (sử dụng clang -O2 -fno-vectorize từ Clang vectorizes ngay cả ở -O2) nó chỉ biến mất sử dụng nó sau mỗi cuộc gọi đến clock_gettime.

Dưới đây là đoạn code tôi sử dụng để kiểm tra điều này (với GCC 6.3 và Clang 3,8)

#include <string.h> 
#include <stdio.h> 
#include <x86intrin.h> 
#include <time.h> 

void fix_avx() { __asm__ __volatile__ ("vzeroupper" : : :); } 
void fix_sse() { } 
void (*fix)(); 

double get_wtime() { 
    struct timespec time; 
    clock_gettime(CLOCK_MONOTONIC, &time); 
    #ifndef __AVX__ 
    fix(); 
    #endif 
    return time.tv_sec + 1E-9*time.tv_nsec; 
} 

void dispatch() { 
    fix = fix_sse; 
    #if defined(__INTEL_COMPILER) 
    if (_may_i_use_cpu_feature (_FEATURE_AVX)) fix = fix_avx; 
    #else 
    #if defined(__GNUC__) && !defined(__clang__) 
    __builtin_cpu_init(); 
    #endif 
    if(__builtin_cpu_supports("avx")) fix = fix_avx; 
    #endif 
} 

#define N 1000000 
#define R 1000 

void mul(double *a, double *b) { 
    for (int i = 0; i<N; i++) a[i] *= b[i]; 
} 

int main() { 
    dispatch(); 
    const double mem = 3*sizeof(double)*N*R/1024/1024/1024; 
    const double maxbw = 34.1; 

    double *a = (double*)_mm_malloc(sizeof *a * N, 32); 
    double *b = (double*)_mm_malloc(sizeof *b * N, 32); 

    //b must be initialized to get the correct bandwidth!!! 
    memset(a, 1, sizeof *a * N); 
    memset(b, 1, sizeof *b * N); 

    double dtime; 
    //dtime = get_wtime(); // call once to fix GCC 
    //printf("%f\n", dtime); 
    //fix = fix_sse; 

    dtime = -get_wtime(); 
    for(int i=0; i<R; i++) mul(a,b); 
    dtime += get_wtime(); 
    printf("time %.2f s, %.1f GB/s, efficency %.1f%%\n", dtime, mem/dtime, 100*mem/dtime/maxbw); 

    _mm_free(a), _mm_free(b); 
} 

Nếu tôi vô hiệu hóa lười biếng độ phân giải chức năng cuộc gọi với -z now (ví dụ clang -O2 -fno-vectorize -z now foo.c) sau đó Clang chỉ cần __asm__ __volatile__ ("vzeroupper" : : :); sau cuộc gọi đầu tiên đến clock_gettime giống như GCC.

Tôi mong đợi rằng với -z now Tôi chỉ cần __asm__ __volatile__ ("vzeroupper" : : :); ngay sau main() nhưng tôi vẫn cần sau cuộc gọi đầu tiên đến clock_gettime.

+1

Mã đẹp! Từ [trang web gcc này] (https://gcc.gnu.org/onlinedocs/gcc/x86-Built-in-Functions.html), tôi không rõ ràng là bạn phải gọi '__builtin_cpu_init (void)' trước khi gọi '__builtin_cpu_supports (" avx ")' hay không. Bạn đã kiểm tra mã của mình trên một cpu không phải AVX cũ? – wim

+1

@wim, 'dispatch' không được nhận xét. Đó là vì tôi đã kiểm tra GCC chỉ cần gọi 'vzeroupperonce' thay vì mọi cuộc gọi. Tôi không biết về '__builtin_cpu_init'. Nó hoạt động mà không có nó (mặc dù tôi không có một hệ thống mà không có AVX để kiểm tra). Tôi thêm nó vào câu trả lời của tôi chỉ để được an toàn. –

+0

'_dl_runtime_resolve_avx' chỉ được gọi là ** tại cuộc gọi đầu tiên ** đối với một số chức năng từ tệp thư viện được chia sẻ khác nhau. Hãy thử vô hiệu hóa ràng buộc lười biếng (http://man7.org/linux/man-pages/man1/ld.1.html - "lazy .. yêu cầu trình liên kết động trì hoãn độ phân giải cuộc gọi chức năng đến điểm khi hàm được gọi (lười ràng buộc), thay vì ở thời gian tải. Ràng buộc lười là mặc định. ") với' xuất LD_BIND_NOW = 1' (http://man7.org/linux/man-pages/man8/ld.so.8.html - " giải quyết tất cả các biểu tượng khi khởi động chương trình thay vì trì hoãn ") để tắt tính năng gọi' _dl_runtime_resolve_avx' khi chạy. – osgx

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