计算AVX2向量中每个元素的前导零位,模拟_mm256_lzcnt_epi32

在AVX512中,有一个固有的_mm256_lzcnt_epi32,它返回一个向量,对于8个32位元素中的每个元素,该向量包含输入向量元素中前导零位的数量。

有没有一种仅使用AVX和AVX2指令来实现此目标的有效方法?

当前,我正在使用一个循环,该循环提取每个元素并应用_lzcnt_u32函数。


相关:要对一个大的位图进行位扫描,请参见Count leading zeros in __m256i word,该位图使用pmovmskb->位扫描可以找到要对哪个字节进行标量位扫描。

这个问题是关于当您实际上要使用全部8个结果而不仅仅是选择一个时,对8个单独的32位元素进行8个单独的lzcnts的操作。

GoDolphinboy 回答:计算AVX2向量中每个元素的前导零位,模拟_mm256_lzcnt_epi32

float代表指数格式的数字,因此int-> FP转换为我们提供了指数字段中编码的最高置位位置。

我们希望int-> float的幅度四舍五入到向下(将值截断为0),而不是默认的最近舍入值。可能会四舍五入,使0x3FFFFFFF看起来像0x40000000。如果您在不进行任何FP数学运算的情况下进行了很多此类转换,则可以将MXCSR 1 中的舍入模式设置为截断,然后在完成后将其设置回去。

否则,您可以使用v & ~(v>>8)来保留8个最高有效位,并将某些或所有较低位保持零,包括MSB下方可能设置的第8位。这足以确保所有舍入模式都不会舍入到2的下一个幂。它总是保持8个最高有效位,因为v>>8移入8个零,所以倒数就是8个。在低位位置,无论MSB处于什么位置,都会从高位开始经过8个零,因此它将永远不会清除任何整数的最高有效位。根据MSB下方的设置位排列方式,它可能会或可能不会清除以下8个最高有效位。

转换后,我们在位模式上使用整数移位将指数(和符号位)移至底部,并通过饱和减法消除偏置。如果在原始32位输入中未设置任何位,则使用min将结果设置为32。

__m256i avx2_lzcnt_epi32 (__m256i v) {
    // prevent value from being rounded up to the next power of two
    v = _mm256_andnot_si256(_mm256_srli_epi32(v,8),v); // keep 8 MSB

    v = _mm256_castps_si256(_mm256_cvtepi32_ps(v)); // convert an integer to float
    v = _mm256_srli_epi32(v,23); // shift down the exponent
    v = _mm256_subs_epu16(_mm256_set1_epi32(158),v); // undo bias
    v = _mm256_min_epi16(v,_mm256_set1_epi32(32)); // clamp at 32

    return v;
}

脚注1:fp-> int转换可使用截断(cvtt),但int-> fp转换仅可使用默认舍入(取决于MXCSR)。

AVX512F为512位向量引入了舍入模式替代,它将解决问题__m512 _mm512_cvt_roundepi32_ps( __m512i a,int r);。但是,所有带有AVX512F的CPU也都支持AVX512CD,因此您可以只使用_mm512_lzcnt_epi32。对于AVX512VL,_mm256_lzcnt_epi32

,

@aqrit的答案似乎更聪明地使用了FP比特hacks 。我在下面的答案是基于我首先找到的一个用于标量的bithack,因此它并未尝试避免double(比int32宽,这对于SIMD是一个问题) )。

它使用HW签名的int-> float转换和饱和整数减来处理设置的MSB(负浮点数),而不是将位填充到尾数中以进行手动uint-> double。如果您可以将MXCSR设置为在其中的许多_mm256_lzcnt_epi32中向下取整,则效率更高。


https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogIEEE64Float建议将整数填充到大double的尾数中,然后减去以得到FPU硬件以获得标准化的double。 (我认为这有点神奇,uint32_t-> double How to efficiently perform double/int64 conversions with SSE/AVX?中使用了@Mysticial解释的技术(适用于uint64_t最多2 52 -1)

然后抓住double的指数位并消除偏差。

我认为整数log2与lzcnt是相同的东西,但可能以2的幂乘以1。

Standford Graphics比特黑客页面列出了您可以使用的其他无分支比特黑客,它们可能仍然比8倍标量lzcnt更好。

如果您知道自己的数字始终很小(例如小于2 ^ 23),则可以使用float进行此操作,并避免拆分和混合。

  int v; // 32-bit integer to find the log base 2 of
  int r; // result of log_2(v) goes here
  union { unsigned int u[2]; double d; } t; // temp

  t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] = 0x43300000;
  t.u[__FLOAT_WORD_ORDER!=LITTLE_ENDIAN] = v;
  t.d -= 4503599627370496.0;
  r = (t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] >> 20) - 0x3FF;
     

上面的代码通过将整数存储在尾数中(而指数设置为252)来加载具有32位整数(无填充位)的64位(IEEE-754浮点数)双精度数。铸造的双精度数减去252(表示为双精度数),这会将结果指数设置为输入值v的对数2。剩下的就是将指数位移到位置(右20位)并减去偏差,即0x3FF(十进制为1023)。

要在AVX2上执行此操作,请将set1_epi32(0x43300000)_mm256_castps_pd的奇/偶半数混合并平移和混合以获得__m256d _mm256_castpd_si256并将下半部分/上半部分移位/混合到位,然后遮罩以得到指数。

在AVX2上,对FP位模式进行整数运算非常有效,在FP数学指令的输出上进行整数移位时,旁路延迟只有1个周期的额外延迟。

(待办事项:使用C ++内在函数编写,编辑欢迎语或其他人可以将其发布为答案。)


我不确定您是否可以使用int-> double 转换进行任何操作,然后读取指数字段。负数没有前导零,而正数给出的指数取决于幅度。

如果您确实希望这样做,则可以一次走一条128位通道,改组以供xmm-> ymm压缩int32_t->压缩double转换。

,

该问题也被标记为AVX,但是AVX中没有整数处理的说明,这意味着需要在支持AVX但不{ {1}}。我在下面显示了经过详尽测试的版本,但是有点行人版本。这里的基本思想与其他答案一样,前导零的计数由整数到浮点转换期间发生的浮点归一化确定。结果的指数与前导零的计数一一对应,只是在参数为零的情况下结果是错误的。从概念上讲:

AVX2

其中clz (a) = (158 - (float_as_uint32 (uint32_to_float_rz (a)) >> 23)) + (a == 0) 是重新解释的强制转换,而float_as_uint32()是从无符号整数到带截断的浮点的转换。正常的舍入转换可能会将转换结果提高到下一个2的幂,从而导致前导零位的计数不正确。

uint32_to_float_rz()既不提供单条截断整数到浮点数的转换,也不提供无符号整数的转换。此功能需要被仿真。只要不改变转换结果的大小,模拟就不需要精确。截断部分由aqrit's answer invert-右移-andn 技术处理。要使用有符号转换,我们在转换前将数字减半,然后在转换后加倍并递增:

SSE

此方法在下面的float approximate_uint32_to_float_rz (uint32_t a) { float r = (float)(int)((a >> 1) & ~(a >> 2)); return r + r + 1.0f; } 中转换为SSE内在函数。

sse_clz()
本文链接:https://www.f2er.com/3115715.html

大家都在问