首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >AVX2和AVX512的加速

AVX2和AVX512的加速
EN

Stack Overflow用户
提问于 2020-02-04 06:02:05
回答 1查看 1.5K关注 0票数 0

我正在尝试可视化将AVX2和AVX512合并的加速过程。

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <immintrin.h>
#include <omp.h>
#include <time.h>
int main()
{
  long i, N = 160000000;
  int * A = (int *)aligned_alloc(sizeof(__m256), sizeof(int) * N);
  int * B = (int *)aligned_alloc(sizeof(__m256), sizeof(int) * N);
  int * C = (int *)aligned_alloc(sizeof(__m256), sizeof(int) * N);

  int * E = (int *)aligned_alloc(sizeof(__m512), sizeof(int) * N);
  int * F = (int *)aligned_alloc(sizeof(__m512), sizeof(int) * N);
  int * G = (int *)aligned_alloc(sizeof(__m512), sizeof(int) * N);

  srand(time(0));

  for(i=0;i<N;i++)
  {
    A[i] = rand();
    B[i] = rand();
    E[i] = rand();
    F[i] = rand();
  }

  double time = omp_get_wtime();
  for(i=0;i<N;i++)
  {
    C[i] = A[i] + B[i];
  }
  time = omp_get_wtime() - time;
  printf("General Time taken %lf\n", time);

  __m256i A_256_VEC, B_256_VEC, C_256_VEC;
  time = omp_get_wtime();
  for(i=0;i<N;i+=8)
  {
    A_256_VEC = _mm256_load_si256((__m256i *)&A[i]);
    B_256_VEC = _mm256_load_si256((__m256i *)&B[i]);
    C_256_VEC = _mm256_add_epi32(A_256_VEC, B_256_VEC);
    _mm256_store_si256((__m256i *)&C[i],C_256_VEC);
  }
  time = omp_get_wtime() - time;
  printf("AVX2 Time taken %lf\n", time);

  free(A);
  free(B);
  free(C);

  __m512i A_512_VEC, B_512_VEC, C_512_VEC;
  time = omp_get_wtime();
  for(i=0;i<N;i+=16)
  {
    A_512_VEC = _mm512_load_si512((__m512i *)&E[i]);
    B_512_VEC = _mm512_load_si512((__m512i *)&F[i]);
    C_512_VEC = _mm512_add_epi32(A_512_VEC, B_512_VEC);
    _mm512_store_si512((__m512i *)&G[i],C_512_VEC);
  }
  time = omp_get_wtime() - time;
  printf("AVX512 Time taken %lf\n", time);

  for(i=0;i<N;i++)
  {
    if(G[i] != E[i] + F[i])
    {
      printf("Not Matched !!!\n");
      break;
    }
  }
  free(E);
  free(F);
  free(G);

  return 1;
}

因此,代码是分三个阶段分发的。有三个数组。它只是一个简单的数组加法。首先,我们使用通用循环执行它,然后使用AVX2,然后是AVX512。我正在使用英特尔Xeon 6130处理器。

代码是使用命令编译的,

代码语言:javascript
复制
gcc -o test.o test.c -mavx512f -fopenmp -mavx2

输出是,

代码语言:javascript
复制
General Time taken 0.532550
AVX2 Time taken 0.175549
AVX512 Time taken 0.264475

现在,在一般循环和内部实现中,加速比是可见的。但是时间从AVX2增加到AVX512,这在理论上是不应该的。

我已经检查了单独的加载、添加、存储操作。AVX512的存储操作占用最大的时间。

为了检查是否从两个代码段中删除了存储操作,结果是,

代码语言:javascript
复制
General Time taken 0.530248
AVX2 Time taken 0.115234
AVX512 Time taken 0.107062

有人能对这种行为有所启发吗?或者这是意料之中的事?

*更新1*

在使用-O3 -march=native扩展进行编译之后,新的时间表是,

代码语言:javascript
复制
General Time taken 0.014887
AVX2 Time taken 0.008072
AVX512 Time taken 0.014630

这些都是所有的加载,添加,存储指令。

*更新2*

测试1 :

一般循环已被修改如下,

代码语言:javascript
复制
for(i=0;i<N;i++)
{
    //C[i] = A[i] + B[i];
    //G[i] = E[i] + F[i];
}

输出是,

代码语言:javascript
复制
General Time taken 0.000003
AVX2 Time taken 0.014877
AVX512 Time taken 0.014334

因此,在这两种情况下,页面错误都会发生。

测试2 :

一般循环已被修改如下,

代码语言:javascript
复制
for(i=0;i<N;i++)
{
    C[i] = A[i] + B[i];
    G[i] = E[i] + F[i];
}

因此,缓存是在这两种情况下完成的。

输出是,

代码语言:javascript
复制
General Time taken 0.029703
AVX2 Time taken 0.008500
AVX512 Time taken 0.008560

测试3 :

在所有场景中都添加了一个虚拟外部循环,并且N的大小被缩减为160000

代码语言:javascript
复制
for(j=0;j<N;j++)
{
    for(i=0;i<N;i+= /* 1 or 8 or 16 */)
    {
         // Code
    }
}

现在输出是,

代码语言:javascript
复制
General Time taken 6.969532
AVX2 Time taken 0.871133
AVX512 Time taken 0.447317
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-02-04 06:50:31

您的AVX2测试重用了已经用“通用”测试编写的数组。所以已经有页错了。

您的AVX512测试正在写入一个尚未被触摸的数组,并且必须支付时间区域中这些页面结果的成本。要么将其移到定时区域之外,要么再次重用C[]。或者mmap(MAP_POPULATE)也能工作,连接可写的页面。(对于现实世界的使用来说,懒惰的页面错误可能更好。让内核在编写前几页为零可能会降低总成本,因为在内核的零存储写回外部缓存之前,可以让真正的写操作在L1d缓存中命中。

注意到,“通用”时间(用于自动向量化的第一个循环)几乎与"AVX512“时间相同。(与gcc -O3 -march=native一起,GCC将使用256位向量自动向量化”通用“循环,这是-mprefer-vector-width=256 for -march=skylake-avx512的默认调优)。

这些循环所做的工作基本上是一样的:读取2个初始化数组,并编写一个尚未触及的数组,从而导致页面错误。

较低的时钟速度使用512位矢量(限制最大涡轮)不应该大大降低内存带宽。(这种2读/1写访问模式将导致内存瓶颈。)如果解核(L3 / mesh)速度减慢,以匹配最快的核心,这可能会减少一些带宽,但如果存在的话,效果似乎很小。

这种类似流的测试的内存带宽应该与256 vs512位矢量的内存带宽基本相同。如果您想看到从512位向量中可以测量到的加速比,对于每个内存带宽计算如此之少的问题,您将需要您的数组来适应L1d缓存,并且已经很热了。或者可能是L2缓存。(在对数组进行迭代的内环周围使用一个重复循环,这样就可以运行足够长的时间,从而达到良好的计时精度)。AVX2可以很容易地与L3或内存保持同步,这样AVX512就不会帮助处理大数组,除非您在每个向量上做更多的工作。

启用优化(https://godbolt.org/z/w4zcrC)后,asm循环没有什么奇怪之处,所以我不得不仔细看看您实际编写的数组。

甚至在AVX2循环运行之前,A和B就可能被从缓存中完全逐出(因为您的N太大了;ABC各有662个MiB )。但是,为AVX2和AVX512插入不同的数组,而不运行任何热身循环以确保CPU处于最大涡轮时,这仍然有点奇怪。

“一般”时间基本上是C[]数组中时钟速度和页面错误的热身循环,因此它所测量的实际时间并不表示写入已经脏的内存的内存带宽。您可以使用perf查看在内核中花费了多少时间。

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60051453

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档