我们找到了替换std::sqrt
(Timing Square Root)的各种技巧和std::exp
(Using Faster Exponential Approximation)的一些技巧,但我找不到任何替换std::log
。< / p>
它是我程序中循环的一部分,并且多次被调用,而exp和sqrt已经过优化,英特尔VTune现在建议我优化std::log
,之后似乎只有我的设计选择才会限制。
现在我使用ln(1+x)
与x
之间的-0.5
和+0.5
之间的3阶泰勒逼近(90%的情况下最大误差为4%)否则回落到std::log
。这让我加速了15%。
答案 0 :(得分:9)
在开始设计和部署针对性能的超越功能的定制实现之前,强烈建议在算法级别以及通过工具链进行优化。不幸的是,我们没有关于要在此优化的代码的任何信息,也没有关于工具链的信息。
在算法级别,检查是否真正需要所有对超越函数的调用。也许存在需要较少函数调用的数学变换,或者将超越函数转换为代数运算。任何超越函数调用都可能是多余的,例如因为计算是不必要地进出对数空间?如果精度要求适中,整个计算是否可以单一精度执行,使用float
而不是double
?在大多数硬件平台上,避免double
计算可以显着提高性能。
编译器倾向于提供各种影响数字密集型代码性能的开关。除了将常规优化级别增加到-O3
之外,通常还有一种方法可以关闭非正常支持,即打开flush-to-zero或FTZ模式。这在各种硬件平台上具有性能优势。此外,通常会有一个“快速数学”标志,其使用会导致精度略有降低,并消除处理特殊情况(如NaN和无穷大)的开销,以及errno
的处理。一些编译器还支持代码的自动矢量化,并附带SIMD数学库,例如英特尔编译器。
对数函数的自定义实现通常涉及将二进制浮点参数x
分离为指数e
和尾数m
,以便x = m * 2
e
,因此log(x) = log(2) * e + log(m)
。选择m
使其接近统一,因为这提供了有效的近似值,例如log(m) = log(1+f) = log1p(f)
minimax polynomial approximation。
C ++提供frexp()
函数将浮点操作数分隔为尾数和指数,但实际上,通常使用更快的机器特定方法,通过重新解释它们来操作位级别的浮点数据作为相同大小的整数。下面的单精度对数代码logf()
演示了两种变体。函数__int_as_float()
和__float_as_int()
用于将int32_t
重新解释为IEEE-754 binary32
浮点数,反之亦然。此代码严重依赖于大多数当前处理器,CPU或GPU上的硬件中直接支持的融合乘法 - 加法运算FMA。在fmaf()
映射到软件仿真的平台上,此代码的速度会慢得令人无法接受。
#include <cmath>
#include <cstdint>
/* compute natural logarithm, maximum error 0.85756 ulps */
float my_logf (float a)
{
float m, r, s, t, i, f;
int32_t e;
if ((a > 0.0f) && (a <= 3.40282347e+38f)) { // 0x1.fffffep+127
#if PORTABLE
m = frexpf (a, &e);
if (m < 0.666666667f) {
m = m + m;
e = e - 1;
}
i = (float)e;
#else // PORTABLE
i = 0.0f;
/* fix up denormal inputs */
if (a < 1.175494351e-38f){ // 0x1.0p-126
a = a * 8388608.0f; // 0x1.0p+23
i = -23.0f;
}
e = (__float_as_int (a) - 0x3f2aaaab) & 0xff800000;
m = __int_as_float (__float_as_int (a) - e);
i = fmaf ((float)e, 1.19209290e-7f, i); // 0x1.0p-23
#endif // PORTABLE
/* m in [2/3, 4/3] */
f = m - 1.0f;
s = f * f;
/* Compute log1p(f) for f in [-1/3, 1/3] */
r = fmaf (-0.130187988f, f, 0.140889585f); // -0x1.0aa000p-3, 0x1.208ab8p-3
t = fmaf (-0.121489584f, f, 0.139809534f); // -0x1.f19f10p-4, 0x1.1e5476p-3
r = fmaf (r, s, t);
r = fmaf (r, f, -0.166845024f); // -0x1.55b2d8p-3
r = fmaf (r, f, 0.200121149f); // 0x1.99d91ep-3
r = fmaf (r, f, -0.249996364f); // -0x1.fffe18p-3
r = fmaf (r, f, 0.333331943f); // 0x1.5554f8p-2
r = fmaf (r, f, -0.500000000f); // -0x1.000000p-1
r = fmaf (r, s, f);
r = fmaf (i, 0.693147182f, r); // 0x1.62e430p-1 // log(2)
} else {
r = a + a; // silence NaNs if necessary
if (a < 0.0f) r = 0.0f / 0.0f; // NaN
if (a == 0.0f) r = -1.0f / 0.0f; // -Inf
}
return r;
}
如代码注释中所述,上述实现提供了忠实圆润的单精度结果,并且它处理符合IEEE-754浮点标准的特殊情况。通过消除特殊情况支持,消除对非正规参数的支持以及降低准确性,可以进一步提高性能。这导致以下示例性变体:
/* natural log on [0x1.f7a5ecp-127, 0x1.fffffep127]. Maximum relative error 9.4529e-5 */
float my_faster_logf (float a)
{
float m, r, s, t, i, f;
int32_t e;
e = (__float_as_int (a) - 0x3f2aaaab) & 0xff800000;
m = __int_as_float (__float_as_int (a) - e);
i = (float)e * 1.19209290e-7f; // 0x1.0p-23
/* m in [2/3, 4/3] */
f = m - 1.0f;
s = f * f;
/* Compute log1p(f) for f in [-1/3, 1/3] */
r = fmaf (0.230836749f, f, -0.279208571f); // 0x1.d8c0f0p-3, -0x1.1de8dap-2
t = fmaf (0.331826031f, f, -0.498910338f); // 0x1.53ca34p-2, -0x1.fee25ap-2
r = fmaf (r, s, t);
r = fmaf (r, s, f);
r = fmaf (i, 0.693147182f, r); // 0x1.62e430p-1 // log(2)
return r;
}
答案 1 :(得分:3)
答案 2 :(得分:0)
#include <math.h>
#include <iostream>
constexpr int LogPrecisionLevel = 14;
constexpr int LogTableSize = 1 << LogPrecisionLevel;
double log_table[LogTableSize];
void init_log_table() {
for (int i = 0; i < LogTableSize; i++) {
log_table[i] = log2(1 + (double)i / LogTableSize);
}
}
double fast_log2(double x) { // x>0
long long t = *(long long*)&x;
int exp = (t >> 52) - 0x3ff;
int mantissa = (t >> (52 - LogPrecisionLevel)) & (LogTableSize - 1);
return exp + log_table[mantissa];
}
int main() {
init_log_table();
double d1 = log2(100); //6.6438561897747244
double d2 = fast_log2(100); //6.6438561897747244
double d3 = log2(0.01); //-6.6438561897747244
double d4 = fast_log2(0.01); //-6.6438919626096089
}
答案 3 :(得分:0)
我对@njuffa 的回答进行了矢量化处理。自然对数,适用于 AVX2:
inline __m256 mm256_fmaf(__m256 a, __m256 b, __m256 c){
return _mm256_add_ps(_mm256_mul_ps(a, b), c);
}
//https://stackoverflow.com/a/39822314/9007125
//https://stackoverflow.com/a/65537754/9007125
// vectorized version of the answer by njuffa
/* natural log on [0x1.f7a5ecp-127, 0x1.fffffep127]. Maximum relative error 9.4529e-5 */
inline __m256 fast_log_sse(__m256 a){
__m256i aInt = *(__m256i*)(&a);
__m256i e = _mm256_sub_epi32( aInt, _mm256_set1_epi32(0x3f2aaaab));
e = _mm256_and_si256( e, _mm256_set1_epi32(0xff800000) );
__m256i subtr = _mm256_sub_epi32(aInt, e);
__m256 m = *(__m256*)&subtr;
__m256 i = _mm256_mul_ps( _mm256_cvtepi32_ps(e), _mm256_set1_ps(1.19209290e-7f));// 0x1.0p-23
/* m in [2/3, 4/3] */
__m256 f = _mm256_sub_ps( m, _mm256_set1_ps(1.0f) );
__m256 s = _mm256_mul_ps(f, f);
/* Compute log1p(f) for f in [-1/3, 1/3] */
__m256 r = mm256_fmaf( _mm256_set1_ps(0.230836749f), f, _mm256_set1_ps(-0.279208571f) );// 0x1.d8c0f0p-3, -0x1.1de8dap-2
__m256 t = mm256_fmaf( _mm256_set1_ps(0.331826031f), f, _mm256_set1_ps(-0.498910338f) );// 0x1.53ca34p-2, -0x1.fee25ap-2
r = mm256_fmaf(r, s, t);
r = mm256_fmaf(r, s, f);
r = mm256_fmaf(i, _mm256_set1_ps(0.693147182f), r); // 0x1.62e430p-1 // log(2)
return r;
}
答案 4 :(得分:-3)
这取决于你需要多准确。通常会调用log来了解数字的大小,通过检查浮点数的指数字段,您可以基本上免费进行。这也是你的第一个近似值。我将为我的书&#34; Basic Algorithms&#34;插入一个插件。它解释了如何从第一原理实现标准库数学函数。