我掌握了一个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的幂。
现在,如果min
和max
使得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位整数乘法的高部分(尽管这不做矢量化形式)