[英]Fastest way for wrapping a value in an interval
我很好奇如何將浮點值x
包裝在半閉區間[0; a[
[0; a[
。
例如,我可以有一個任意的實數,比如x = 354638.515
,我希望將其折疊成[0; 2π[
[0; 2π[
因為我對該范圍有一個很好的sin
近似值。
fmod
標准 C 函數在我的基准測試中顯示相當高,通過檢查各種 libc 實現的源代碼,我可以理解為什么:這件事相當分支,可能是為了處理很多 IEEE754 特定問題:
-ffast-math
運行會導致 GCC 生成通過 x86/x86_64 上的 x87 FPU 的代碼,這會帶來一系列問題(例如 80 位雙精度數、FP state 和其他有趣的事情)。 我希望實現至少半正確地向量化,如果可能的話,不是通過 x87 FPU 向量化 go,而是至少通過向量寄存器,因為我的代碼的 rest 最終被編譯器向量化,即使不一定是最佳方式. 就我而言,我只關心通常的實數值,而不是 NaN,也不是無窮大。 我的范圍在編譯時也是已知的並且是正常的(常見的情況是π/2
),因此不需要檢查“特殊情況”,例如 range == 0 。
因此,對於該特定用例,什么是fmod
的良好實現?
假設范圍是常數且為正,您可以計算它的倒數以避免代價高昂的除法。
void fast_fmod(float * restrict dst, const float * restrict src, size_t n, float divisor) {
float reciprocal = 1.0f / divisor;
for (size_t i = 0; i < n; ++i)
dst[i] = src[i] - divisor * (int)(src[i] * reciprocal);
}
帶有簡單演示的最終代碼是:
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
void fast_fmod(float * restrict dst, const float * restrict src, size_t n, float divisor) {
float reciprocal = 1.0f / divisor;
for (size_t i = 0; i < n; ++i)
dst[i] = src[i] - divisor * (int)(src[i] * reciprocal);
}
int main() {
float src[9] = {-4, -3, -2, -1, 0, 1, 2, 3, 4};
float dst[9];
float div = 3;
fast_fmod(dst, src, 9, div);
for (int i = 0; i < 9; ++i) {
printf("fmod(%g, %g) = %g vs %g\n", src[i], div, dst[i], fmod(src[i], div));
}
}
產生預期的 output:
fmod(-4, 3) = -1 vs -1
fmod(-3, 3) = 0 vs -0
fmod(-2, 3) = -2 vs -2
fmod(-1, 3) = -1 vs -1
fmod(0, 3) = 0 vs 0
fmod(1, 3) = 1 vs 1
fmod(2, 3) = 2 vs 2
fmod(3, 3) = 0 vs 0
fmod(4, 3) = 1 vs 1
使用命令編譯 GCC:
$ gcc prog.c -o prog -O3 -march=haswell -lm -fopt-info-vec
prog.c:8:4: optimized: loop vectorized using 32 byte vectors
prog.c:8:4: optimized: loop vectorized using 32 byte vectors
prog.c:8:30: optimized: basic block part vectorized using 32 byte vectors
因此代碼被很好地矢量化了。
編輯
看起來 CLANG 在向量化此代碼方面做得更好:
401170: c5 fc 10 24 8e vmovups (%rsi,%rcx,4),%ymm4
401175: c5 fc 10 6c 8e 20 vmovups 0x20(%rsi,%rcx,4),%ymm5
40117b: c5 fc 10 74 8e 40 vmovups 0x40(%rsi,%rcx,4),%ymm6
401181: c5 fc 10 7c 8e 60 vmovups 0x60(%rsi,%rcx,4),%ymm7
401187: c5 6c 59 c4 vmulps %ymm4,%ymm2,%ymm8
40118b: c5 6c 59 cd vmulps %ymm5,%ymm2,%ymm9
40118f: c5 6c 59 d6 vmulps %ymm6,%ymm2,%ymm10
401193: c5 6c 59 df vmulps %ymm7,%ymm2,%ymm11
401197: c4 41 7e 5b c0 vcvttps2dq %ymm8,%ymm8
40119c: c4 41 7e 5b c9 vcvttps2dq %ymm9,%ymm9
4011a1: c4 41 7e 5b d2 vcvttps2dq %ymm10,%ymm10
4011a6: c4 41 7e 5b db vcvttps2dq %ymm11,%ymm11
4011ab: c4 41 7c 5b c0 vcvtdq2ps %ymm8,%ymm8
4011b0: c4 41 7c 5b c9 vcvtdq2ps %ymm9,%ymm9
4011b5: c4 41 7c 5b d2 vcvtdq2ps %ymm10,%ymm10
4011ba: c4 41 7c 5b db vcvtdq2ps %ymm11,%ymm11
4011bf: c5 3c 59 c3 vmulps %ymm3,%ymm8,%ymm8
4011c3: c5 34 59 cb vmulps %ymm3,%ymm9,%ymm9
4011c7: c5 2c 59 d3 vmulps %ymm3,%ymm10,%ymm10
4011cb: c5 24 59 db vmulps %ymm3,%ymm11,%ymm11
4011cf: c4 c1 5c 5c e0 vsubps %ymm8,%ymm4,%ymm4
4011d4: c4 c1 54 5c e9 vsubps %ymm9,%ymm5,%ymm5
4011d9: c4 c1 4c 5c f2 vsubps %ymm10,%ymm6,%ymm6
4011de: c4 c1 44 5c fb vsubps %ymm11,%ymm7,%ymm7
4011e3: c5 fc 11 24 8f vmovups %ymm4,(%rdi,%rcx,4)
4011e8: c5 fc 11 6c 8f 20 vmovups %ymm5,0x20(%rdi,%rcx,4)
4011ee: c5 fc 11 74 8f 40 vmovups %ymm6,0x40(%rdi,%rcx,4)
4011f4: c5 fc 11 7c 8f 60 vmovups %ymm7,0x60(%rdi,%rcx,4)
4011fa: 48 83 c1 20 add $0x20,%rcx
4011fe: 48 39 c8 cmp %rcx,%rax
401201: 0f 85 69 ff ff ff jne 401170 <fast_fmod+0x40>
這是我寫的沒有精度損失的 fmod() 替代方案。
如果計數器的指數非常高而分母的指數非常低,計算可能會花費很長時間,但它仍然比當前的 Gnu C 庫實現更快:
#include <stdint.h>
#include <string.h>
#include <fenv.h>
#if defined(_MSC_VER)
#include <intrin.h>
#endif
#if defined(__GNUC__) || defined(__clang__)
#define likely(x) __builtin_expect((x), 1)
#define unlikely(x) __builtin_expect((x), 0)
#else
#define likely(x) (x)
#define unlikely(x) (x)
#endif
#define MAX_EXP (0x7FF)
#define SIGN_BIT ((uint64_t)1 << 63)
#define EXP_MASK ((uint64_t)MAX_EXP << 52)
#define IMPLCIT_BIT ((uint64_t)1 << 52)
#define MANT_MASK (IMPLCIT_BIT - 1)
#define QNAN_BIT (IMPLCIT_BIT >> 1)
inline uint64_t bin( double d )
{
uint64_t u;
memcpy( &u, &d, sizeof d );
return u;
}
inline double dbl( uint64_t u )
{
double d;
memcpy( &d, &u, sizeof u );
return d;
}
inline double NaN()
{
feraiseexcept( FE_INVALID );
return dbl( SIGN_BIT | EXP_MASK | QNAN_BIT );
}
inline void normalize( uint64_t *mant, int *exp )
{
#if defined(__GNUC__) || defined(__clang__)
unsigned bits = __builtin_clz( *mant ) - 11;
*mant <<= bits;
#elif defined(_MSC_VER)
unsigned long bits;
_BitScanReverse64( &bits, *mant );
bits = (bits ^ 63) - 11;
*mant <<= bits;
#else
unsigned bits = 0;
for( ; !(*mant & IMPLCIT_BIT); *mant <<= 1, ++bits );
#endif
*exp -= bits;
}
double myFmodC( double counter, double denominator )
{
uint64_t const
bCounter = bin( counter ),
bDenom = bin( denominator );
uint64_t const sign = bCounter & SIGN_BIT;
if( unlikely((bCounter & EXP_MASK) == EXP_MASK) )
// +/-[Inf|QNaN|SNaN] % ... = -QNaN
return NaN();
if( unlikely((bDenom & EXP_MASK) == EXP_MASK) )
// +/-x % +/-[Inf|QNan|SNaN]
if( likely(!(bDenom & MANT_MASK)) )
// +/-x % +/-Inf = -/+x
return dbl( sign | bCounter & ~SIGN_BIT );
else
// +/-x % +/-[QNaN|SNaN] = -NaN
return NaN();
int
counterExp = bCounter >> 52 & MAX_EXP,
denomExp = bDenom >> 52 & MAX_EXP;
uint64_t
counterMant = (uint64_t)!!counterExp << 52 | bCounter & MANT_MASK,
denomMant = (uint64_t)!!denomExp << 52 | bDenom & MANT_MASK;
if( unlikely(!counterExp) )
// counter is denormal
if( likely(!counterMant) )
// counter == +/-0.0
if( likely(denomMant) )
// +/-0.0 % +/-x = -/+0.0
return dbl( sign );
else
// +/-0.0 % +/-0.0 = -QNaN
return NaN();
else
// normalize counter
normalize( &counterMant, &counterExp ),
++counterExp;
if( unlikely(!denomExp) )
// denominator is denormal
if( likely(!denomMant) )
// +/-x % +/-0.0 = -/+QNaN
return NaN();
else
// normalize denominator
normalize( &denomMant, &denomExp ),
++denomExp;
int remExp = counterExp;
uint64_t remMant = counterMant;
for( ; ; )
{
int below = remMant < denomMant;
if( unlikely(remExp - below < denomExp) )
break;
remExp -= below;
remMant <<= below;
if( unlikely(!(remMant -= denomMant)) )
{
remExp = 0;
break;
}
normalize( &remMant, &remExp );
};
if( unlikely(remExp <= 0) )
// denormal result
remMant >>= -remExp + 1,
remExp = 0;
return dbl( sign | (uint64_t)remExp << 52 | remMant & MANT_MASK );
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.