英特尔至强融核使用的内在函数是不是比自动矢量化获得更好的性能?

Posted

技术标签:

【中文标题】英特尔至强融核使用的内在函数是不是比自动矢量化获得更好的性能?【英文标题】:Is Intel Xeon Phi used intrinsics get good performance than Auto-Vectorization?英特尔至强融核使用的内在函数是否比自动矢量化获得更好的性能? 【发布时间】:2014-05-20 10:58:35 【问题描述】:

英特尔至强融核提供使用“IMCI”指令集, 我用它来做 "c = a*b" ,像这样:

float* x = (float*) _mm_malloc(N*sizeof(float), ALIGNMENT) ;
float* y = (float*) _mm_malloc(N*sizeof(float), ALIGNMENT) ;
float z[N];
_Cilk_for(size_t i = 0; i < N; i+=16)

    __m512 x_1Vec = _mm512_load_ps(x+i);
    __m512 y_1Vec = _mm512_load_ps(y+i);

    __m512 ans = _mm512_mul_ps(x_1Vec, y_1Vec);
    _mm512_store_pd(z+i,ans);


并测试它的性能,当 N SIZE 为 1048576 时, 它需要花费 0.083317 秒,我想将性能与自动矢量化进行比较 所以其他版本代码是这样的:

_Cilk_for(size_t i = 0; i < N; i++)
    z[i] = x[i] * y[i];

此版本花费 0.025475 秒(但有时花费 0.002285 或更少,我不知道为什么?) 如果我将_Cilk_for 改为#pragma omp parallel for,性能会很差。

那么,如果答案是这样的,为什么我们需要使用内在函数? 我是否在任何地方犯了任何错误? 有人可以给我一些优化代码的好建议吗?

【问题讨论】:

您使用的是哪个编译器?自动矢量化不是由 CPU 本身 AFAIK 执行的,它取决于优化 我使用了 intel'c icpc 编译器,并使用了 -O3 和 -vec-report3 选项,我确定循环是自动矢量化,但我想知道自动矢量化是否比Intrinsics,为什么我们需要 Intrinsics? 我不是这个领域的专家,但自动矢量化是一种编译器优化,这意味着:编译器将尝试找到一个模式并应用它是否适合您的代码。如果您事先知道一个内在函数会适合它,那么您只需使用它。如果你做对了,它们可能是等效的,如果你做错了,你可能会得到更差的表现。 非常感谢!因此,如果我知道使用内在函数的正确方法,我将获得比自动矢量化或相等的更好的性能,对吧?但事实上,恰恰相反。我对此非常不解。 为什么 z 64 位不对齐? software.intel.com/sites/products/documentation/doclib/iss/2013/… 【参考方案1】:

由于各种错误,测量的意义不大。

代码将 16 个浮点数存储为 8 个双精度数。 _mm512_store_pd 应该是 _mm512_store_ps。 代码在地址 z+i 的未对齐位置上使用 _mm512_store_...,这可能会导致分段错误。使用__declspec(align(64)) 解决此问题。 数组 x 和 y 未初始化。这可能会引入随机数的非规范值,这可能会影响性能。 (我不确定这是否是英特尔至强融核的问题)。 没有证据表明使用了 z,因此优化器可能会删除计算。我认为这里不是这种情况,但是像这样的微不足道的基准测试存在风险。 此外,在堆栈上分配大数组可能会导致堆栈溢出。 单次运行示例可能是一个糟糕的基准测试,因为时间可能主要由_Cilk_for 的 fork/join 开销支配。假设有 120 个 Cilk worker(60 个 4 路线程核心的默认值),每个 worker 只有大约 1048576/120/16 = ~546 次迭代。时钟频率超过 1 GHz,不会花很长时间。事实上,循环中的工作是如此之小,以至于一些工人很可能永远没有机会窃取工作。这可能解释了 _Cilk_for 跑赢 OpenMP 的原因。在 OpenMP 中,所有线程都必须参与 fork/join 才能完成并行区域。

如果编写测试以纠正所有错误,它本质上就是在一个大数组上计算 z[:] = x[:]*y[:]。由于英特尔(R) 至强融核(TM) 上的宽向量单元,这成为对内存/高速缓存带宽的测试,而不是 ALU 速度,因为 ALU 完全有能力超过内存带宽。

内在函数对于不能表示为并行/simd 循环的东西很有用,通常是需要花哨排列的东西。例如,我使用内部函数在 MIC 上执行 16 元素 prefix-sum operation(如果我没记错的话,只有 6 条指令)。

【讨论】:

这些都是 OP 应该考虑的优点 (+1)。但是关于使用带有前缀和的内在函数,我已经完成了***.com/questions/19494114/…,但最终它与本示例中的点积并没有太大不同:它是内存/缓存绑定而不是计算绑定。所以 SIMD(带有内在函数)对于大型数组没有多大帮助。 在大核机器上,是的,前缀和受内存限制,对矢量化基本上没有意义。但英特尔至强融核的硬件线程较慢,但向量更宽,因此 6 指令前缀和可以得到回报,速度几乎是标量版本的 2 倍,即使对于不适合缓存的数组也是如此。 这很有趣!我希望我有机会在某个时候与 Xeon Phi 合作。我想知道这是否适用于在 Broadwell 之后推出的 AVX512 内核。【参考方案2】:

下面我的回答同样适用于英特尔至强和英特尔至强融核。

    Intrinsics-bases 解决方案是最“强大”的,就像“像”汇编编码一样。 但不利的一面是,基于内在函数的解决方案通常不是(大多数)可移植的,而不是“生产力”- 面向的方法,通常不适用于已建立的“遗留”软件代码库。 此外,它通常要求程序员是低级甚至是微架构专家。 但是,有一些替代内在函数/汇编代码的方法。他们是: A) 自动向量化(当编译器识别某些模式并自动生成向量代码时) B) “显式”或用户引导的向量化(当程序员就向量化的内容和条件等方面向编译器提供一些指导时;显式向量化通常意味着使用关键字或编译指示) C) 使用 VEC 类或其他类型的内在函数包装库,甚至是非常专业的编译器。事实上,就生产力和遗留代码增量更新而言,2.C 通常与内部编码一样糟糕)

在您的第二个代码 sn-p 中,您似乎使用了“显式”矢量化,这在使用 Intel Compiler 和 GCC4.9 的所有最新版本支持的 Cilk Plus 和 OpenMP4.0“框架”时目前可以实现。 (我说您似乎使用显式矢量化,因为 Cilk_for 最初是为了多线程而发明的,但是最新版本的英特尔编译器可能会自动并行化矢量化循环,当使用 cilk_for 时)

【讨论】:

以上是关于英特尔至强融核使用的内在函数是不是比自动矢量化获得更好的性能?的主要内容,如果未能解决你的问题,请参考以下文章

我们如何知道英特尔至强融核协处理器是不是存在

英特尔至强融核协处理器是不是支持硬件级别的图形处理?

英特尔至强融核上的动态内存变慢

英特尔至强融核上的 MKL 3D 双精度复数 FFT

英特尔至强融核中的排列

如果在 Xeon Phi 上编译时不知道循环计数,则性能下降