最小SIMD矢量宽度数据类型

时间:2018-03-28 20:05:29

标签: c vectorization hardware intel simd

我试图理解我可以将多少内容打包到矢量硬件中。以英特尔AVX-512硬件为例,我可以将8个双打(64位)或16个单独(32位)放入我的矢量中。但是,如果我在64位计算机上运行,​​那么我的默认指针大小可能是64位。因此,如果我想要取消引用指针(或者只是使用数组语法访问和数组),那么这将需要64位整数运算。这似乎告诉我,在64位机器上,我可以拥有的最小分区是64位数据类型。

然后考虑我下面的MWE,我希望编译器看到我只处理32位对象(或更小)。鉴于我可以预期减少/计算(假设我正在做更多计算密集且带宽有限的事情),如果我可以使用64位数据类型将矢量划分为32位数据类型,则可以在一半的时间内完成。

  • 有没有办法查询编译器使用的向量分区的粒度? (避免挖掘生成的组件)。
  • 在64位计算机上,如果假定64位内存地址,向量如何分区为小于64位的数据类型?
  

在我看来,如果我有向量寄存器并且我想进行向量运算,那么如果我需要n向量寄存器,其中每个寄存器被分成m - 位的数据类型,那么我希望矢量化的任何代码段都不能使用大于m的数据类型。 (?)

MWE

使用icc 18.0.0与-mkl -O2 -qopenmp -qopt-report进行编译,优化报告会验证for循环向量化。

#include <stdlib.h>
#include <stdio.h>

#define N 1024

int main(int argc, char **argv)
{
    unsigned int a[N];
    for (unsigned int i = 0; i < N; i++) a[i] = i;
    unsigned int z[N];
    unsigned int *b = a;
    printf("Sizes (Bytes)\n");
    printf("Pointer      = %d\n", sizeof(b));
    printf("Unsigned int = %d\n", sizeof(*b));
    printf("Array        = %d\n\n", sizeof(a));

    unsigned int sum = 0;
    #pragma omp simd reduction(+:sum)
    for (unsigned int i = 0; i < N; i++)
    {
        z[i] = 4 * a[i];
        unsigned int squares = a[i] * a[i]; // Possibly some more complex sequence of operations.
        sum += squares;
    }

    for (unsigned int i = 0; i < N; i += N/4) printf("z[%d] = %d\n", i, z[i]);
    printf("\nsum  = %d\n", sum);    
}

我的机器上的输出是:

Sizes (Bytes)
Pointer      = 8
Unsigned int = 4
Array        = 4096

z[0] = 0
z[256] = 1024
z[512] = 2048
z[768] = 3072

sum  = 357389824

2 个答案:

答案 0 :(得分:3)

  

这似乎告诉我,在64位计算机上,我可以拥有的最小分区是64位数据类型。

这个假设是错误的。

用(尴尬)类比来说明,邮政地址的长度(以符号表示)与房屋的大小无关。指针的宽度与它引用的数据大小无关。

lower 绑定了在给定类型的硬件上如何处理小块数据。它在现代机器上被称为 byte (8位a.k.a. octet ,但它也可以像古代一样使用10或6位)。但是,通常没有更高的绑定。在英特尔64中,作为一个例子,XSAVE系列指令引用了一个长度接近4千字节的内存块,具有相同的32/64位指针。

  

以英特尔AVX-512硬件为例,我可以将8个双打(64位)或16个单独(32位)装入我的矢量。

或者你可以适应32个半浮点数(16位)或64个字节。不确定是否有AVX-512指令在半字节(4位块)上运行。

  

有没有办法查询编译器使用的向量分区的粒度? (避免挖掘生成的组件)。

同样,编译器选择的下限取决于程序中所选数据类型的宽度。如果您使用int,则粒度将至少为sizeof(int)字节,如果long - sizeof(long)等,则不太可能使用超出必要范围的类型,因为它会导致应该考虑的机器指令的语义差异。例如,如果编译器出于未知原因选择使用分区为uint64_t块的SIMD向量来操作uint32_t块的向量,那么它必须隐藏溢出行为的差异,并且这将导致性能损失。

我不知道是否有OMP编译指示来查询此类信息。考虑到同一个二进制文件可能在运行时动态选择多个代码路径(程序的启动,至少被英特尔编译器使用所谓的调度),这是不太可能的,因此编译时查询是不可能的,我看不到多少在运行时查询中使用。

  

在64位计算机上,如果假定64位内存地址,向量如何分区为小于64位的数据类型?

只是机器指令以不同方式解释相同的SIMD寄存器。在英特尔64中,作为一个例子,有各种各样的例子(例子来自最近的英特尔软件开发手册):

  • VDBPSADBW- 无符号字节(8位)上的双块包装求和 - 绝对差值(SAD)
  • VCVTPH2PS - 将 16位 FP值转换为单精度FP值
  • VCVTPS2UDQ转换打包单精度(32位)浮点值到打包无符号双字整数值
  • VCVTQQ2PD转换打包四字(64位)整数到打包的双精度浮点值

答案 1 :(得分:0)

  

有没有办法查询向量分区的粒度   编译器使用过? (避免挖掘结果   组件)。

我认为您问题的最佳答案是:&#34;从选择报告或Vectorization Advisor等工具中了解您的矢量长度是什么?

VL有几种可能的定义:

  • 当矢量化时,编译器选择最佳&#34;矢量长度&#34; (VL)即可。我们称它为#34;编译器定义的循环VL&#34;。 循环 VL可以定义为多个标量迭代&#34;打包到&#34;结果向量运算。因此,在简单的非FMA DP AVX-512情况下,VL通常等于4。
  • VL也可以不是为循环定义,而是为了指令(或 不知何故,对于指令数据操作数),然而&#34;最佳&#34;或者&#34;当前&#34; 每指令 VL可能与所得到的循环VL明显不同。

  • 您可能还会想到另一个指标,即硬件定义的循环 (或指示)VL。

可以放入单个向量寄存器的数据元素的数量,考虑到目标ISA,精度等 - 几乎等于硬件定义的每指令VL。 编译器定义的每循环VL可能经常高于或低于&#34;主导&#34;每指令硬件定义的VL。对于具有不同数据类型混合的循环或者针对微架构进行额外优化的循环,尤其如此,使用寄存器分割(不溢出)或多泵浦等技术。

在使用英特尔编译器重新编译代码时,要了解循环的VL,请使用-qopt-report标志。

了解循环和指令的编译器定义和硬件定义的VL(不仅适用于英特尔编译器),以及时间指标,步幅,二进制ISA静态分析(如果您最终需要它,甚至可以进行汇编) ),FLOPS和AVX指令混合数据 - 使用英特尔顾问(调查分析):enter image description here