【发布时间】:2022-01-02 17:49:49
【问题描述】:
我目前正在尝试使用 AVX2 实现 XOR_SHIFT 随机数生成器,它实际上非常简单且非常快速。但是我需要能够指定一个范围。这通常需要模数。
这对我来说是一个主要问题,原因有两个:
- 将 _mm256_rem_epu32() / _mm256_rem_epi32() SVML 函数添加到我的代码中,我的循环运行时间从大约 270 毫秒减少到 1.8 秒。哎哟!
- SVML 仅适用于 MSVC 和 Intel 编译器
是否有任何使用 AVX2 进行模运算显着更快的方法?
非向量代码:
std::srand(std::time(nullptr));
std::mt19937_64 e(std::rand());
uint32_t seed = static_cast<uint32_t>(e());
for (; i != end; ++i)
{
seed ^= (seed << 13u);
seed ^= (seed >> 7u);
seed ^= (seed << 17u);
arr[i] = static_cast<T>(low + (seed % ((up + 1u) - low)));
}//End for
矢量化:
constexpr uint32_t thirteen = 13u;
constexpr uint32_t seven = 7u;
constexpr uint32_t seventeen = 17u;
const __m256i _one = _mm256_set1_epi32(1);
const __m256i _lower = _mm256_set1_epi32(static_cast<uint32_t>(low));
const __m256i _upper = _mm256_set1_epi32(static_cast<uint32_t>(up));
__m256i _temp = _mm256_setzero_si256();
__m256i _res = _mm256_setzero_si256();
__m256i _seed = _mm256_set_epi32(
static_cast<uint32_t>(e()),
static_cast<uint32_t>(e()),
static_cast<uint32_t>(e()),
static_cast<uint32_t>(e()),
static_cast<uint32_t>(e()),
static_cast<uint32_t>(e()),
static_cast<uint32_t>(e()),
static_cast<uint32_t>(e())
);
for (; (i + 8uz) < end; ++i)
{
//Generate Random Numbers
_temp = _mm256_slli_epi32(_seed, thirteen);
_seed = _mm256_xor_si256(_seed, _temp);
_temp = _mm256_srai_epi32(_seed, seven);
_seed = _mm256_xor_si256(_seed, _temp);
_temp = _mm256_slli_epi32(_seed, seventeen);
_seed = _mm256_xor_si256(_seed, _temp);
//Narrow
_temp = _mm256_add_epi32(_upper, _one);
_temp = _mm256_sub_epi32(_temp, _lower);
_temp = _mm256_rem_epu32(_seed, _temp); //Comment this line out for a massive speed up but incorrect results
_res = _mm256_add_epi32(_lower, _temp);
_mm256_store_si256((__m256i*) &arr[i], _res);
}//End for
【问题讨论】:
-
对于编译时间常数范围,使用乘法逆进行除法。例如,就像我在What's the fastest way to generate a 1 GB text file containing random digits? 中所做的那样,将 AVX2 xorshift+ 输出中的熵切碎为 base-10 数字,使用 GNU C 向量扩展让编译器生成实际的乘法常数和立即移位。 (当然,您可以将该 asm 移植回内部函数以与其他编译器一起使用。)在我的情况下,16 位块很有用,这是 x86 的元素大小具有 SIMD 乘法。
-
几乎是 Vectorized Ranged Random number generation across all types 的副本,其中提到了 libdivide。对于您的原始标题,只是在不讨论随机数的情况下进行模运算,它将是 SSE integer division? 的副本,其中提到了 libdivide 和 VCL(github.com/vectorclass/version2/blob/… - VCL 具有乘法逆运算,其方式适用于常量传播和提升没有循环)。
-
stackoverflow.com/questions/30790184/… 由 njuffa 评论的 Montgomery 的论文显示了通用算法(或少数算法,因为某些模不需要所有后校正步骤)除以倒数乘法。因此,最好制作一个能返回最优序列的生成器...
-
另外,你知道你可以做到
__m256i range = _mm256_set1_epi32(arg2 - arg1 + 1),对吧?不需要鼓励愚蠢的编译器(cough MSVC)在循环内部或根本在 SIMD 寄存器中实际执行那些循环不变操作。 -
而不是
rand() % range,您通常会使用(rand() * range) / RAND_MAX获得类似分布良好的数字(这在很大程度上取决于您的RNG)。对于 32 位整数,这对于 AVX2 来说也不是很简单,但它应该比计算余数要快得多。
标签: c++ random simd modulo avx