将随机整数转换为范围[min,max],而不进行分支



我掌握了一个SUPER-FAST算法,该算法均匀地生成一个随机字节数组。它比std库的c++均匀分布和mersenne扭曲器快6倍。

数组的计数可以被4整除,因此它可以被解释为整数数组。将每个条目强制转换为一个整数,将生成范围为[INT_MIN, INT_MAX]的值。但是,我如何将这些整数值转换为位于我自己的[min, maximum]之间呢?

我想避免任何if else,以避免分支。


也许我应该应用一些逐位逻辑,丢弃每个数字中不相关的位?(因为所有剩余的未屏蔽位都将是0或1)。如果我能提取最大值中的最高有效位,我就可以在整数中屏蔽任何比该位更重要的位。

例如,如果我希望我的max是17,那么它是二进制形式的00010001。也许我的掩码会看起来是00011111?然后我可以将它应用于数组中的所有数字。

但是,这个面具是错误的。。。它实际上允许高达(1+2+4+8+16)的值:(

我能做什么?此外,如何照顾min

编辑

我在应用程序的每一帧中都会为神经网络生成数百万个数字。我设法使用AXV2为浮点变量对代码进行了矢量化(使用本文),但也需要使整数工作。

但是我如何将这些整数值转换为位于我自己的[min, maximum]之间?

由于范围可能不是2的幂,所以位屏蔽已经失效,但您已经发现了。

Modulo也已经过时了,它在AVX2中并不是一个本机操作(即使它确实存在,也不一定会使它高效)。

还有另一种选择:使用_mm256_mul_epu32乘高(不幸的是,32位数字没有"纯"乘高,就像16位数字一样,所以我们只能使用只做50%有用功的运算)。这里的想法是取输入数x(全范围)和所需范围r,然后计算r * x / 2^32,其中除法是隐式的(通过取乘积的高半部分来实现)。

如果x / 2^32被解释为有理数,那么它将是[0.0...1.0)(不包括1.0)中的一个数,乘以r,然后将范围扩展为[0.0..r)(不包含r)。这不是它的计算方式,但这就是公式的来源。

通过将min添加到缩放结果,可以容易地设置范围的最小值。

代码中(经过轻微测试):

__m256i squish(__m256i x, int min, int max) {
__m256i sizeOfRange = _mm256_set1_epi32((unsigned)max - min);
__m256i scaled_even = _mm256_shuffle_epi32(_mm256_mul_epu32(x, sizeOfRange), 0xB1);
__m256i scaled_odd = _mm256_mul_epu32(_mm256_shuffle_epi32(x, 0xB1), sizeOfRange);
__m256i scaled = _mm256_blend_epi32(scaled_even, scaled_odd, 0xAA);
return _mm256_add_epi32(scaled, _mm256_set1_epi32(min));
}

它仍然是一个独占范围,它不能处理完整的[INT_MIN .. INT_MAX]作为输出范围。甚至没有办法指定它,它最多只能做[INT_MIN .. INT_MAX)(或者例如具有零偏移的等效范围:[0 .. -1))。

它也不是真的均匀的,因为简单的基于模的范围缩减不是真的均匀的,你只是不能在K仓上公平地划分N弹珠,除非K碰巧均匀地划分了N

核心思想是使用模而不是逐位掩码,这在非2次幂的情况下是无用的。没有分支也是一个有点奇怪的要求。你想要的是";足够快";,而不是";无分支和逐位掩码";。

所以假设我们有一个函数

int rand();

其均匀地产生随机整数。如果max的形式为2^n-1,则以下

rand() % (max+1)

将均匀地产生范围为CCD_ 27的随机整数。这是因为整数的总数是2的幂。

现在,如果minmax使得max-min具有2^n-1的形式,则下面的

(rand() % (max-min+1)) + min

将均匀地产生范围为CCD_ 32的随机整数。

但是当max-min不是2^n-1形式时会发生什么呢?那我们就没运气了。(rand() % (max-min+1)) + min方法仍然会产生[min, max]范围内的随机整数,但不再是一致的。为什么?因为当n是固定的,而不是2的幂时,则给出具体r = x % n结果的整数总数根据r而变化。

不过,这个方法还不错。CCD_ 40值越大,越接近均匀分布,在实践中通常足够好。它非常快,没有分支。

另一个例子是

upper = get_upper_power_of_2(max - min)
do
{
tmp = rand() % upper;
} while (tmp > max - min);
result = tmp + min;

该方法具有很好的性质,即它是一致的,但它没有停止性质,即理论上该算法可能永远不会停止。它也有分支。但在实践中,它确实会很快停止(可能性很大),因此它是一种非常常见的算法。例如,它在标准Java库中。

当然,当max-min溢出时(即当min是一个大负数时),这两种方法都有一个问题,如果我们切换到无符号整数,然后再切换回整数,这是可以解决的。

据我所知,当max不是来自01均匀生成器的2^n-1形式时,没有算法在[0, max]中生成随机整数,使得结果是均匀的并且具有停止性质。我认为不可能存在这样的算法,但我未能在计算机科学中找到合适的结果。

如果一个值中有2^N个随机位,可以通过以下操作将其放入整数范围:

r = ((value * (max-min)) >> N) + min

实际上,您将您的值视为带乘法的分数。保证您在"[最小…最大)"中获得值

这最终是两个可向量化的操作:mulhi,"添加">

r = _mm256_add_epi16(
_mm256_mulhi_epi16(value, _mm256_set1_epi16(max-min)), 
_mm256_set1_epi16(min));

尽管如果你想要32位,看起来你需要两个mul_epi32和一个shuffle才能得到结果。

对于64位值,请参阅:获取64位整数乘法的高部分(尽管这不做矢量化形式)

相关内容

  • 没有找到相关文章

最新更新