我想在C ++中支持以下操作:
void generate_random_simd(T* array, T upper_bound, T lower_bound) {
// uses simd instructions for rng in range [lower_bound, upper_bound]
}
类型T可以是任何uint,int或float类型-32或64位。是否可以直接获得有效的实施方法或有关此材料的一些文献?
我确实找到了一些实现,例如this和this。但是它们不支持上述所有类型,也不支持提供上下限。使用它们可能反过来又需要额外的处理才能达到其结果,恐怕这最终会导致开销等同于简单地循环并使用标准的C ++随机数生成器(非SIMD)。
答案 0 :(得分:1)
元素边界仅在您具有下限/上限时才重要。 否则,对于整数,您只需要SIMD向量中的128或256位随机数据。
例如,您可以使用SSE2 / AVX2 xorshift +,它在64位SIMD元素中运行多个xorshift +生成器。当您想真正使用某物的随机数据时,可以将其视为16x uint8_t
或2x uint64_t
或两者之间的任何值。
这是一个将其用作16位元素的示例->多个十进制数字矢量,在我对What's the fastest way to generate a 1 GB text file containing random digits?的回答中,对unix.SE进行了回答。 (用C语言编写,带有Intel内部函数,Core 2,Haswell和Skylake基准数字。)
它的运行速度足够快,您可以在高速缓存中仍然很热的时候使用输出,例如L1d命中的4或8 kiB块的缓存块。或者只是在生成随机数时使用它们。
您当然可以使用其他除数,并向每个元素添加一些东西以获得除0..upper之外的范围。但是,使用编译时常数范围最有效。尽管如此,您仍可以使用运行时变量将libdivide用于SIMD除法(或模)。
在上下界未知的情况下,您可能只想将输入向量用于一个结果向量。当我针对最大速度进行优化时,有必要处理16位整数中的多个0..9位数字,以节省xorshift +的工作量。 0..9只是0..65535的一小部分,因此还有很多熵,并且与第一个余数具有不同的偏差。
FP比整数更难,因为某些位模式表示NaN 。而且,您通常希望沿实数线均匀分布,而不是有限位模式的均匀分布。 (所有可表示的float
值中有一半的幅度小于1.0。得到的值越接近零,则float
的距离就越近。)
显然,通常会生成[0,1.0)
范围内的统一FP随机数。 (可表示的总值的1/4。)将N乘以[0, N)
并乘以N可以很好地满足N <2 ^ 24的要求,但是如果超过此范围,则开始失去熵并引入偏差{{3} }。
根据您范围的大小,在我看来,通过组合23位随机有效数字(尾数)来在[1.0, 2.0)
范围(或任何其他单指数范围)中生成它们要容易得多。 ),并具有固定的指数/符号位。
虽然熵的位数更少,但是却非常统一,可以使用SIMD _mm_and_ps
和_mm_or_ps
来完成。 (不幸的是,有效位数只有23位宽,不是8或16的倍数,因此我们不能简单地使用_mm_blendv_epi8
或_mm_blend_epi16
)
如果您想要的分布不是统一的(according to Daniel Lemire's article, "How many floating-point numbers are in the interval [0,1]?"),例如高斯或泊松,您必须为此找到一种算法。
由于必需的分支,带有拒绝的采样不适用于SIMD。您可以做2个随机数的候选向量,然后将它们无分支地合并,然后在仍然需要拒绝的情况下分支。
也许左打包未拒绝的候选者会让您相当有效地用随机数填充缓冲区,从而在每次迭代中生成一个可变数。有关SSE2 / AVX2 / AVX512左包装的信息,请参见https://en.wikipedia.org/wiki/Random_number_generation#Generation_from_a_probability_distribution。
同样,将缓冲区的块大小保持足够小,以使您在循环返回时获得L1d或至少L2高速缓存命中。