C中的ARM Neon:如何在使用内在函数时组合不同的128位数据类型?

时间:2017-04-20 12:13:09

标签: c arm vectorization neon

TLTR

对于arm内在函数,如何将uint8x16_t类型的128位变量提供给期望uint16x8_t的函数?

<小时/> 扩展版

上下文:我有一个灰度图像,每像素1个字节。我想将其缩减2倍。对于每个2x2输入框,我想采用最小像素。在普通的C中,代码将如下所示:

for (int y = 0; y < rows; y += 2) {
    uint8_t* p_out = outBuffer + (y / 2) * outStride;
    uint8_t* p_in = inBuffer + y * inStride;
    for (int x = 0; x < cols; x += 2) {
         *p_out = min(min(p_in[0],p_in[1]),min(p_in[inStride],p_in[inStride + 1]) );
         p_out++;
         p_in+=2;
    }
}

其中row和cols都是2的倍数。我称“stride”为从一个像素到图像中紧邻下方的像素的字节步长。

现在我想要对此进行矢量化。这个想法是:

  1. 连续拍摄两行像素
  2. 从顶行加载a中的16个字节,并在b
  3. 中加载下面的16个字节
  4. 计算ab之间的最小字节数。存储在a
  5. 创建a的副本,将其右移1个字节(8位)。将其存储在b
  6. 计算ab之间的最小字节数。存储在a
  7. a的每个第二个字节存储在输出图像中(丢弃一半字节)
  8. 我想用Neon内在函数来写这个。好消息是,每一步都存在与之相匹配的内在。

    例如,在第3点,可以使用(来自here):

    uint8x16_t  vminq_u8(uint8x16_t a, uint8x16_t b);
    

    在第4点,可以使用8位移位(来自here)使用以下之一:

    uint16x8_t vrshrq_n_u16(uint16x8_t a, __constrange(1,16) int b);
    uint32x4_t vrshrq_n_u32(uint32x4_t a, __constrange(1,32) int b);
    uint64x2_t vrshrq_n_u64(uint64x2_t a, __constrange(1,64) int b);
    

    那是因为我不关心字节1,3,5,7,9,11,13,15会发生什么,因为无论如何它们将从最终结果中丢弃。 (这个问题的正确性已得到验证,这不是问题的重点。)

    但是,vminq_u8的输出属于uint8x16_t类型,并且与我想要使用的移位内在函数不兼容。在C ++中,我用this templated data structure解决了问题,而我被告知问题cannot be reliably addressed using union (编辑:虽然答案是指C ++,实际上是in C type punning IS allowed,也不是using pointers to cast,因为这会破坏严格的别名规则。

    使用ARM Neon内在函数时,组合不同数据类型的方法是什么?

1 个答案:

答案 0 :(得分:2)

对于这类问题,arm_neon.h提供vreinterpret{q}_dsttype_srctype转换操作符。

  

在某些情况下,您可能希望将矢量视为具有   不同类型,不改变其价值。一组内在函数是   提供执行此类转换。

因此,假设ab被声明为:

uint8x16_t a, b;

您的观点4可以写成(*)

b = vreinterpretq_u8_u16(vrshrq_n_u16(vreinterpretq_u16_u8(a), 8) );

但是,请注意,遗憾的是,这并未使用矢量类型数组来处理数据类型,请参阅ARM Neon: How to convert from uint8x16_t to uint8x8x2_t?

<子> (*)应该说,这是等效的(在这个特定的上下文中)SSE代码更麻烦,因为SSE只有一个128位整数数据类型(即__m128i):

__m128i b = _mm_srli_si128(a,1);