[英]Fastest precise way to convert a vector of integers into floats between 0 and 1
考慮一個隨機生成的__m256i
向量。 是否有更快的精確方法將它們轉換為__m256
向量,介於0
(包含)和1
(僅)之間,而不是float(1ull<<32)
1
float(1ull<<32)
?
這是我到目前為止所嘗試的,其中iRand
是輸入, ans
是輸出:
const __m256 fRand = _mm256_cvtepi32_ps(iRand);
const __m256 normalized = _mm256_div_ps(fRand, _mm256_set1_ps(float(1ull<<32)));
const __m256 ans = _mm256_add_ps(normalized, _mm256_set1_ps(0.5f));
與使用_mm256_div_ps
初始版本相比,下面的版本應該更快
vdivps
非常慢,例如在我的Haswell Xeon上,它的周期為18-21周期,吞吐量為14周期。 較新的CPU表現更好BTW,Skylake為11/5,Ryzen為10/6。
正如評論中所說,通過用乘法替換除法並通過FMA進一步改進,可以解決性能問題。 該方法的問題在於分發質量。 如果您嘗試通過舍入模式或剪切在輸出間隔中獲取這些數字,則會在輸出數字的概率分布中引入峰值。
我的實現也不理想,它不會在輸出間隔中輸出所有可能的值,跳過許多可表示的浮點數,尤其是接近0.但是至少分布非常均勻。
__m256 __vectorcall randomFloats( __m256i randomBits )
{
// Convert to random float bits
__m256 result = _mm256_castsi256_ps( randomBits );
// Zero out exponent bits, leave random bits in mantissa.
// BTW since the mask value is constexpr, we don't actually need AVX2 instructions for this, it's just easier to code with set1_epi32.
const __m256 mantissaMask = _mm256_castsi256_ps( _mm256_set1_epi32( 0x007FFFFF ) );
result = _mm256_and_ps( result, mantissaMask );
// Set sign + exponent bits to that of 1.0, which is sign=0, exponent=2^0.
const __m256 one = _mm256_set1_ps( 1.0f );
result = _mm256_or_ps( result, one );
// Subtract 1.0. The above algorithm generates floats in range [1..2).
// Can't use bit tricks to generate floats in [0..1) because it would cause them to be distributed very unevenly.
return _mm256_sub_ps( result, one );
}
更新:如果您想要更好的精度,請使用以下版本。 但它不再是“最快的”。
__m256 __vectorcall randomFloats_32( __m256i randomBits )
{
// Convert to random float bits
__m256 result = _mm256_castsi256_ps( randomBits );
// Zero out exponent bits, leave random bits in mantissa.
const __m256 mantissaMask = _mm256_castsi256_ps( _mm256_set1_epi32( 0x007FFFFF ) );
result = _mm256_and_ps( result, mantissaMask );
// Set sign + exponent bits to that of 1.0, which is sign=0, exponent = 2^0.
const __m256 one = _mm256_set1_ps( 1.0f );
result = _mm256_or_ps( result, one );
// Subtract 1.0. The above algorithm generates floats in range [1..2).
result = _mm256_sub_ps( result, one );
// Use 9 unused random bits to add extra randomness to the lower bits of the values.
// This increases precision to 2^-32, however most floats in the range can't store that many bits, fmadd will only add them for small enough values.
// If you want uniformly distributed floats with 2^-24 precision, replace the second argument in the following line with _mm256_set1_epi32( 0x80000000 ).
// In this case you don't need to set rounding mode bits in MXCSR.
__m256i extraBits = _mm256_and_si256( randomBits, _mm256_castps_si256( mantissaMask ) );
extraBits = _mm256_srli_epi32( extraBits, 9 );
__m256 extra = _mm256_castsi256_ps( extraBits );
extra = _mm256_or_ps( extra, one );
extra = _mm256_sub_ps( extra, one );
_MM_SET_ROUNDING_MODE( _MM_ROUND_DOWN );
constexpr float mul = 0x1p-23f; // The initial part of the algorithm has generated uniform distribution with the step 2^-23.
return _mm256_fmadd_ps( extra, _mm256_set1_ps( mul ), result );
}
首先,沒有除法,用乘法代替它。 雖然@Soonts可能對你來說足夠好,但我只能注意到由於使用映射到[1 ... 2]區間,它產生k / 2 -23形式的統一二元有理數,這是可能的一半生成。 我更喜歡來自S.Vigna的方法(在底部),k / 2 -24形式的所有二元有理數同樣可能。
代碼,VC ++ 2019,x64,Win10,Intel i7 Skylake
#include <random>
#include "immintrin.h"
auto p256_dec_u32(__m256i in) -> void {
alignas(alignof(__m256i)) uint32_t v[8];
_mm256_store_si256((__m256i*)v, in);
printf("v8_u32: %u %u %u %u %u %u %u %u\n", v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]);
}
auto p256_dec_f32(__m256 in) -> void {
alignas(alignof(__m256)) float v[8];
_mm256_store_ps(v, in);
printf("v8_float: %e %e %e %e %e %e %e %e\n", v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]);
}
auto main() -> int {
const float c = 0x1.0p-24f; // or (1.0f / (uint32_t(1) << 24));
const int N = 1000000;
std::mt19937 rng{ 987654321ULL };
__m256 sum = _mm256_set1_ps(0.0f);
for (int k = 0; k != N; ++k) {
alignas(alignof(__m256i)) uint32_t rnd[8] = { rng(), rng(), rng(), rng(), rng(), rng(), rng(), rng() };
__m256i r = _mm256_load_si256((__m256i*)rnd);
__m256 q = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_srli_epi32(r, 8)), _mm256_set1_ps(c));
sum = _mm256_add_ps(sum, q);
}
sum = _mm256_div_ps(sum, _mm256_set1_ps((float)N)); // computing average
p256_dec_f32(sum);
return 0;
}
與輸出
5.002970e-01 4.997833e-01 4.996118e-01 5.004955e-01 5.002163e-01 4.997193e-01 4.996586e-01 5.001499e-01
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.