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

Posted

技术标签:

【中文标题】将随机整数转换为范围 [min,max] 而不进行分支【英文标题】:Transform random integers into range [min,max] without branching 【发布时间】:2021-01-04 10:57:55 【问题描述】:

我得到了hold on an SUPER-FAST algorithm,它统一生成一个随机字节数组。比标准库的 c++ 均匀分布和 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),但也需要让整数工作。

【问题讨论】:

您使用的是哪个 C++ 版本?值是否保证为正? 既然你用的是C++,why not using C++? 如果 minmax 不是 2 的幂,我猜位掩码没用。 [min, max]中生成一个随机数,相当于在[0,max-min]中生成一个随机数,然后加上min。这减少了在[0,max] 中生成数字的问题。如果max 的形式为2^n-1,那么您可以切断多余的位。但如果不是,则没有算法可以在[0,max] 中从01 统一生成器生成随机数,这样结果是统一的并且它具有停止属性。还要忘记避免分支。 典型的解决方案是rand() % max,这是不统一的。另一个是do result = rand(); while (result > max),它是统一的,但没有停止属性。如果我没记错的话,即使预期的迭代次数大约是 6。 【参考方案1】:

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

它也不是真的统一的,出于同样的原因,简单的基于模的范围缩小并不是真正统一的,你不能公平地将N弹珠划分为K垃圾箱,除非@ 987654337@ 恰好平分N

【讨论】:

谢谢!为了再次向未来的用户强调这一点,这会产生不包括 [min, max) max 范围内的数字。而且,此代码默认也适用于unsigned int :) 我们是否需要 sizeofRange 行中的(无符号)强制转换?我删除了它,它没有工作,包括负整数 @Kari 这并不重要,但无论如何它应该是免费的。我只是把它放在那里以防止任何签名的溢出恶作剧【参考方案2】:

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

所以假设我们有一个函数

int rand();

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

rand() % (max+1)

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

现在如果minmax 是这样的max-min2^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 是一个很大的负数),这两种方法当然都会出现问题,如果我们切换到无符号整数然后再返回整数,可以解决这个问题。

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

【讨论】:

所以你不把do/while循环算作分支? @Caleb 我当然愿意。也许我应该明确地说出来。固定。 我的错 — 我将 它非常快,没有分支 与紧随其后的代码混为一谈,但您明确表示这是一个不同的解决方案。对不起!不过,我认为您的编辑是一种改进。 我让 OP 放松了位掩码的想法。我相信您的回答是出于其他(和适当的)原因讨论两个的权力。因此,这个问题并没有成为您回答的目标。否则请接受我的道歉。 这是矢量化的。此处是 AVX2 的草图:godbolt.org/z/hrjrda。对于 AVX-512,可以使用 _mm_ternarylogic_epi32 进行更快的混合。其他一些可能值得尝试的事情是:1. 展开前 N 次迭代的循环,2. 与其只替换不在我们浪费 rng 循环的范围内的值,不如生成 8值并以连续方式存储k<8,我认为这可以通过屏蔽稀疏存储或随机+存储来实现。方法 2. 也会使其“无分支”【参考方案3】:

如果一个值中有 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 和一个随机播放来获得你的结果。

对于 64 位值,请参阅:Getting the high part of 64 bit integer multiplication(尽管它不支持矢量化形式)

【讨论】:

以上是关于将随机整数转换为范围 [min,max] 而不进行分支的主要内容,如果未能解决你的问题,请参考以下文章

使用Random随机生成[min,max]之间的整数:

前端JavaScript获取指定范围内的随机整数

4月6日--求指定范围的随机数

Mysql 随机数

关于Random.Range的范围

使用Random类生成指定范围的随机数