c - 性能 AVX-512 与 MIC 上的自动矢量化(英特尔至强融核协处理器)

标签 c performance vectorization intrinsics

我正在为 MIC(英特尔至强融核协处理器)上的手动矢量化而苦苦挣扎,我正在做一个简单的计算基准测试(实际上是对 CPU 与 MIC 进行基准测试,并分析自动与手动的矢量化效果)。我想试试内在函数的效果。这是我在 CPU 上的问题,我可以观察到使用 m256 内在函数(与没有内在函数的 CPU 相比)性能提高了 30%,但是在带有 m512 的 MIC 上,性能与没有内在函数的 MIC 相同(OpenMP +内在函数),这正常吗?

  • MIC+INTR ~ 3.18 秒
  • MIC ~ 3.19 秒

  • CPU+INTR ~ 4.31 秒

  • CPU ~ 6.47 秒

我使用的选项:(intel编译器)

  • 为 MIC + intrinsic 编译:-O3 -openmp -DWITH_INTR -restrict
  • 为 MIC 编译:-O3 -openmp -restrict

  • 为 CPU + intrinsic 编译:-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(2x8 核 | 32 线程)
  • MIC:Intel(R) Xeon Phi(TM) 协处理器 x100 系列(61 核 | 244 线程)

代码看起来很长,其实是因为有不使用内在函数的计算和256位 vector 和512位 vector 的计算。

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

#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;
}

也许,我遗漏了代码中的某些内容或编译命令中的某些选项。

我会尝试任何建议。

谢谢。

地球科学

最佳答案

最大的减速之一是您在循环的每次迭代中先执行 _mm_malloc,然后再执行 _mm_free。堆分配非常慢。你最好做一个简单的堆栈分配,即

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

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

也就是说这不是您的主要问题。英特尔编译器很可能很好地矢量化了循环。您应该查看编译器生成的程序集和内在函数,看看其中一个是否“本质上”(抱歉)比另一个更好。如果内部程序集看起来更好,那么您看到的大部分时间可能都被内存访问占用了......

关于c - 性能 AVX-512 与 MIC 上的自动矢量化(英特尔至强融核协处理器),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27945345/

相关文章:

c - 如何使用 lncurses 检测太空入侵者控制台游戏中的子弹击中

更改 C 中结构的变量

javascript - 是时候用 javascript 处理第一个字节了吗?

gcc - 循环中的 BB 太多,无法矢量化是什么意思?

python - Numpy 向量化零阶插值

c - 我们在嵌入式 c 或嵌入式 c++ 中有 '@' 运算符吗?如果是,请向我解释以下代码?

c - 从进程中卸载/删除共享库

java - tomcat服务器上Spring应用程序CPU利用率高

java - 应该使用多个循环或一个循环并检查内部条件

python - 一次获取 NumPy 数组中多行的索引