首页 文章

性能AVX-512与MIC上的AutoVectorization(英特尔至强融核协处理器)

提问于
浏览
1

我正在努力在MIC(intel Xeon Phi Coprocessor)上进行手动矢量化,我正在进行简单的计算基准测试(实际上是对CPU与MIC进行基准测试并分析自动与手动的矢量化效果) . 我想尝试内在函数的效果 . 这是我在CPU上的问题,我可以观察到m256内在函数(相对于没有内在函数的CPU)的性能提升30%,但是对于m512的MIC,性能与没有内在函数的MIC相同(OpenMP内在函数) ),这是正常的吗?

  • MIC INTR~3.18秒

  • MIC~3.19秒

  • CPU INTR~4.31秒

  • CPU~6.47秒

我使用的选项:(英特尔编译器)

  • 编译MIC内在函数:-O3 -openmp -DWITH_INTR -restrict

  • 编译MIC:-O3 -openmp -restrict

  • 编译CPU内在函数:-O3 -openmp -DWITH_INTR -no-offload -restrict

  • 编译CPU:-O3 -openmp -no-offload -restrict

我的硬件配置:

  • CPU:Intel(R)Xeon(R)CPU E5-2680 0 @ 2.70GHz | SandyBridge(2x8cores | 32个主题)

  • MIC:英特尔(R)Xeon Phi(TM)协处理器x100系列(61核| 244线程)

代码似乎很长,但这只是因为没有使用内在函数和256位向量和512位向量的计算进行计算 .

以及想要重现结果的代码:

#include <stdio.h>
#include <omp.h>
#include <offload.h>
#include <math.h>
#include <immintrin.h>

#define N 2<<17
#define P 2<<14

__declspec(target(mic:0)) void testVctr( double * restrict a, double * restrict b, double * restrict c )
{

    double t1( omp_get_wtime() );

    omp_set_num_threads(omp_get_max_threads());

    __assume_aligned( a, 64 );
    __assume_aligned( b, 64 );
    __assume_aligned( c, 64 );

    int i;
    int j;
    int k;

    #ifdef WITH_INTR
        #ifdef __MIC__
            __m512d  n1    = _mm512_set1_pd( 1. );
            __m512d  n1024 = _mm512_set1_pd( 1024. );
            __m512d  n230  = _mm512_set1_pd( 230. );
        #else
            __m256d n1    = _mm256_set1_pd( 1. );
            __m256d n1024 = _mm256_set1_pd( 1024. );
            __m256d n230  = _mm256_set1_pd( 230. );
        #endif
    #endif

    #pragma omp parallel for private( i, j, k ) schedule( dynamic )
    for( i=0; i<N; ++i )
    {
        #ifdef WITH_INTR
            #ifdef __MIC__  
                double * restrict A = (double *restrict) _mm_malloc( (size_t)( (8) * sizeof(double) ), 64 );

                __m512d res   = _mm512_setzero_pd(), r0, r1;

                for( j=0; j<P; j+=8 )
                {
                    r0 = _mm512_load_pd( &b[j] );                   
                    r0 = _mm512_add_pd( r0, n1 );
                    r0 = _mm512_div_pd( n1, r0 );
                    r0 = _mm512_exp_pd( r0 );

                    r1 = _mm512_load_pd( &c[j] );                   
                    r1 = _mm512_mul_pd( r1, n1024 );
                    r1 = _mm512_add_pd( r1, n230 );
                    r1 = _mm512_log_pd( r1 );

                    r0 = _mm512_div_pd( r0, r1 );

                    res = _mm512_add_pd( res, r0 );
                }

                _mm512_store_pd( A, res );

                double tmp(0.);
                for( k=0; k<8; ++k )
                    tmp += A[k];

                a[i] = tmp;

                _mm_free( (double * restrict) A );

            #else
                double * restrict A = (double * restrict) _mm_malloc( (size_t)( (4) * sizeof(double) ), 64 );

                __m256d res   = _mm256_setzero_pd(), r0, r1;

                for( j=0; j<P; j+=4 )
                {
                    r0 = _mm256_load_pd( &b[j] );                   
                    r0 = _mm256_add_pd( r0, n1 );
                    r0 = _mm256_div_pd( n1, r0 );
                    r0 = _mm256_exp_pd( r0 );

                    r1 = _mm256_load_pd( &c[j] );
                    r1 = _mm256_mul_pd( r1, n1024 );
                    r1 = _mm256_add_pd( r1, n230 );
                    r1 = _mm256_log_pd( r1 );

                    r0 = _mm256_div_pd( r0, r1 );

                    res = _mm256_add_pd( res, r0 );
                }

                _mm256_store_pd( A, res );

                double tmp(0.);
                for( k=0; k<4; ++k )
                    tmp += A[k];

                a[i] = tmp;

                _mm_free( (double * restrict) A );

            #endif
        #else
            double res = 0.;

            #pragma simd            
            for( j=0; j<P; ++j )
            {
                double tmp0 = 1./(b[j]+1.);
                double tmp1 = exp( tmp0 );
                double tmp2 = c[j] * 1024;
                double tmp3 = tmp2 + 230;
                double tmp4 = log( tmp3 );
                double tmp5 = tmp1 / tmp4;
                res += tmp5;
            }

            a[i] = res;
        #endif
    }

    printf("\nElapsed time: %f sec\n", omp_get_wtime() - t1 );

}

int main( void )
{
    int i;

    printf("\nOuter loop (N) %d iterations \nInner loop (P) %d iterations\n", N, P );

    double * restrict a = (double * restrict) _mm_malloc( (size_t)( (N) * sizeof(double) ), 64 );
    double * restrict b = (double * restrict) _mm_malloc( (size_t)( (P) * sizeof(double) ), 64 );
    double * restrict c = (double * restrict) _mm_malloc( (size_t)( (P) * sizeof(double) ), 64 ); 

    for( i=0; i<P; ++i )
    {
        b[i] = rand()/RAND_MAX;
        c[i] = rand()/RAND_MAX;
    }

    #pragma offload target( mic : 0 ) \
    out( a : length( N ) align(512) ) \
    in ( b : length( P ) align(512) ) \
    in ( c : length( P ) align(512) ) 
    testVctr( a, b, c );        

    printf( "\nCheck last result: %f (~ 1.)\n", a[N-1]*2./(P) );

    _mm_free( (double * restrict) a );
    _mm_free( (double * restrict) b );
    _mm_free( (double * restrict) c );

    return 0;
}

也许,我错过了代码中的一些东西或编译命令中的一些选项 .

我会尝试任何建议 .

谢谢 .

GS

1 回答

  • 2

    你遇到的最大的减速之一就是你在循环的每次迭代中都是_mm_malloc'ing然后_mm_free'ing . 堆分配非常慢 . 你做一个简单的堆栈分配会好得多,即

    __declspec( align( 64 ) ) double A[8];
    

    这可能会显着提高您的性能,因为完全删除了动态堆管理 .

    那说这不是你的主要问题 . 很可能intel编译器很好地矢量化循环 . 您应该看一下编译器和内在函数生成的程序集,看看是否“内在地”(抱歉)比另一个更好 . 如果内在组件看起来更好,那么你看到的大部分时间都可能被内存访问占用......

相关问题