2016-02-13 118 views
2

我正在寻找一个简单的例子,其中使用至强Phi上的矢量化和并行化,这比只使用Xeon的性能更好。请问你能帮帮我吗?矢量化和并行化至强Phi

我正在尝试下一个例子。我评论的线14,18和19只至强运行和uncoment这些对于至强披,但只有至强比至强-PHI有关自动向量化

1.void main(){ 
2.double *a, *b, *c; 
3.int i,j,k, ok, n=100; 
4.int nPadded = (n%8 == 0 ? n : n + (8-n%8)); 
5.ok = posix_memalign((void**)&a, 64, n*nPadded*sizeof(double)); 
6.ok = posix_memalign((void**)&b, 64, n*nPadded*sizeof(double)); 
7.ok = posix_memalign((void**)&c, 64, n*nPadded*sizeof(double)); 
8.for(i=0; i<n; i++) 
9.{ 
10. a[i] = (int) rand(); 
11. b[i] = (int) rand(); 
12. c[i] = 0.0; 
13.} 
14.#pragma offload target(mic) in(a,b:length(n*nPadded)) inout(c:length(n*nPadded)) 
15.#pragma omp parallel for 
16.for(i = 0; i < n; i++) 
17. for(k = 0; k < n; k++) 
18.  #pragma vector aligned 
19.  #pragma ivdep 
20.  for(j = 0; j < n; j++){ 
21.    c[i*nPadded+j] = c[i*nPadded+j] + a[i*nPadded+k]*b[k*nPadded+j]   
22.} 
+0

尝试产生更多的线程。例如,编写'#pragma omp parallel for num_threads(200)'。 –

+0

'n = 100'是一个* tiny *数组,难怪你没有从并行化中获得更多的加速。如果'卸载'意味着你在一个普通的Haswell或Skylake CPU上启动程序,并且它必须与Xeon-Phi内核进行通信,那么对于一个微型阵列来说这完全是不值得的。 –

回答

0

一两句话更好的性能。自动矢量化的优点是简单。你需要设置一些关键字而不是发生魔法,编译器会为你制作快速代码。如果你想这样试试这个manual

这种方法的缺点是没有简单的方法来理解编译器如何工作。在矢量化报告中,您会看到“LOOP WAS VECTORIZED”或“LOOP WAS NOT VECTORIZED”。但是如果你想真正理解你的代码是如何工作的,唯一的方法就是看你的程序集。这不是组装的问题。你需要用-fcode-asm来编译程序。但我认为如果你需要阅读程序集来检查“简单的自动插件”方法是如何工作的,那么它并不那么简单。

替代自动矢量化的是内在函数(实际上,这不是单一的替代方法)。考虑像使用C函数包装的组件一样的内部函数。许多内部函数内部包装单个装配命令。

我推荐使用这个intrinsics guide

所以我简单的方法步骤:

  1. 让单线程参考实现。您将使用它来检查内部版本的正确性。
  2. 实施SSE内在版本。 SSE内部函数非常简单,可以在Xeon上进行测试。
  3. 实现Xeon Phi的AVX-512版本。
  4. 衡量你的速度。

让我们来做你的程序。 与您的程序有很多不同之处:

  1. 我使用float而不是double。
  2. 我使用_mm_malloc代替posix_memalign。
  3. 我想n被16除以余数(AVX-512向量寄存器中的16个浮点数)。在这个例子中,我不使用循环剥皮。
  4. 我使用本地模式,而不是卸载模式。 KNL是可引导的,因此不再需要使用卸载模式。
  5. 另外我认为你的程序是不正确的,因为它在一段时间内从多个线程修改c数组。但让我们认为这不重要,我们只需要一些计算工作。

我的代码工作时间:

的Intel Xeon 5680

  • 参考计算时间:97.677505秒
  • 内在函数计算时间:6。189296秒

的Intel Xeon披(KNC)SE10X

  • 参考计算时间:199.0秒
  • 内在函数计算时间:2.78秒

代码:

#include <stdio.h> 
#include <omp.h> 
#include <math.h> 
#include "immintrin.h" 
#include <assert.h> 

#define F_E_Q(X,Y,N) (round((X) * pow(10, N)-(Y) * pow(10, N)) == 0) 

void reference(float* a, float* b, float* c, int n, int nPadded); 
void intrinsics(float* a, float* b, float* c, int n, int nPadded); 

char *test(){ 
    int n=4800; 
    int nPadded = n; 

    assert(n%16 == 0); 

    float* a = (float*) _mm_malloc(sizeof(float)*n*nPadded, 64); 
    float* b = (float*) _mm_malloc(sizeof(float)*n*nPadded, 64); 
    float* cRef = (float*) _mm_malloc(sizeof(float)*n*nPadded, 64); 
    float* c = (float*) _mm_malloc(sizeof(float)*n*nPadded, 64); 
    assert(a != NULL); 
    assert(b != NULL); 
    assert(cRef != NULL); 
    assert(c != NULL); 

    for(int i=0, max = n*nPadded; i<max; i++){ 
    a[i] = (int) rand()/1804289408.0; 
    b[i] = (int) rand()/1804289408.0; 
    cRef[i] = 0.0; 
    c[i] = 0.0; 
    } 
    debug_arr("a", "%f", a, 0, 9, 1); 
    debug_arr("b", "%f", b, 0, 9, 1); 
    debug_arr("cRef", "%f", cRef, 0, 9, 1); 
    debug_arr("c", "%f", c, 0, 9, 1); 

    double t1 = omp_get_wtime(); 
    reference(a, b, cRef, n, nPadded); 
    double t2 = omp_get_wtime(); 
    debug("reference calc time: %f", t2-t1); 

    t1 = omp_get_wtime(); 
    intrinsics(a, b, c, n, nPadded); 
    t2 = omp_get_wtime(); 
    debug("Intrinsics calc time: %f", t2-t1); 

    debug_arr("cRef", "%f", cRef, 0, 9, 1); 
    debug_arr("c", "%f", c, 0, 9, 1); 

    for(int i=0, max = n*nPadded; i<max; i++){ 
    assert(F_E_Q(cRef[i], c[i], 2)); 
    } 

    _mm_free(a);     
    _mm_free(b); 
    _mm_free(cRef); 
    _mm_free(c); 
    return NULL; 
} 

void reference(float* a, float* b, float* c, int n, int nPadded){ 
    for(int i = 0; i < n; i++) 
    for(int k = 0; k < n; k++) 
     for(int j = 0; j < n; j++) 
     c[i*nPadded+j] = c[i*nPadded+j] + a[i*nPadded+k]*b[k*nPadded+j];   
} 

#if __MIC__ 

void intrinsics(float* a, float* b, float* c, int n, int nPadded){ 
    #pragma omp parallel for 
    for(int i = 0; i < n; i++) 
    for(int k = 0; k < n; k++) 
     for(int j = 0; j < n; j+=16){ 
     __m512 aPart = _mm512_extload_ps(a + i*nPadded+k, _MM_UPCONV_PS_NONE, _MM_BROADCAST_1X16, _MM_HINT_NONE); 
     __m512 bPart = _mm512_load_ps(b + k*nPadded+j); 
     __m512 cPart = _mm512_load_ps(c + i*nPadded+j); 
     cPart = _mm512_add_ps(cPart, _mm512_mul_ps(aPart, bPart)); 
     _mm512_store_ps(c + i*nPadded+j, cPart); 
     } 
} 

#else 

void intrinsics(float* a, float* b, float* c, int n, int nPadded){ 
    #pragma omp parallel for 
    for(int i = 0; i < n; i++) 
    for(int k = 0; k < n; k++) 
     for(int j = 0; j < n; j+=4){ 
     __m128 aPart = _mm_load_ps1(a + i*nPadded+k); 
     __m128 bPart = _mm_load_ps(b + k*nPadded+j); 
     __m128 cPart = _mm_load_ps(c + i*nPadded+j); 
     cPart = _mm_add_ps(cPart, _mm_mul_ps(aPart, bPart)); 
     _mm_store_ps(c + i*nPadded+j, cPart); 
     } 
} 

#endif 
+0

它看起来最主要的区别是一个更大的'n'(并且直接在KNL上启动程序)。简单的循环不应该需要手动矢量化。另外,如果我理解正确,'#omp parallel for'会将循环切入不同的线程,因此每个线程都会更新'c []'的单独部分。这不是C存储器模型中的数据竞赛;如果不同的线程同时写入(和/或读取)单个'c [i]',它只是一场竞赛。 –

+0

同意更大的n,原生模式和写入c数组。我不知道什么更简单 - 手动或自动矢量化。我看到很多尝试使用自动矢量化结束“但为什么它太慢?”在手动向量化的情况下,我们明确告诉编译器我们需要什么,我们需要良好的程序理解来编写它。 – NtsDK