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

问题描述

我得到了 hold on an SUPER-FAST algorithm,它统一生成一个随机字节数组。比c++均匀分布和std库的mersenne-twister快6倍。

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

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


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

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

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

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

编辑

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

解决方法

但是如何将这些整数值转换为位于我自己的 [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))。

它也不是真正统一的,出于同样的原因,简单的基于模的范围缩减并不是真正统一的,你只是不能在 {{1} 上公平地划分 N 弹珠} 垃圾箱,除非 K 恰好将 K 平均划分。

,

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

假设我们有一个函数

int rand();

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

rand() % (max+1)

将统一产生一个范围为 [0,max] 的随机整数。那是因为整数的总数是 2 的幂。

现在,如果 minmax 使得 max-min 的形式为 2^n-1 那么以下内容

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

将统一产生一个在 [min,max] 范围内的随机整数。

但是当 max-min 不是 2^n-1 形式时会发生什么?那我们就倒霉了。 (rand() % (max-min+1)) + min 方法仍然会在 [min,max] 范围内生成一个随机整数,但不再统一。这是为什么?因为当 n 是固定的而不是 2 的幂时,给出具体 r = x % n 结果的整数总数取决于 r

不过方法还不错。 max-min 值越大,越接近均匀分布,而且通常在实践中已经足够好了。而且速度非常快,没有分支。

另一个例子是

upper = get_upper_power_of_2(max - min)

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

result = tmp + min;

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

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

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

,

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

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

实际上,您将您的值视为乘以的分数。 保证你得到的值是‘[min...max)’

这最终是两个可向量化的操作:mulhi,'add'

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

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

对于 64 位值,请参阅:Getting the high part of 64 bit integer multiplication(虽然这不会做矢量化形式)