与我在Windows上获得的程序相比,在ARMv7上编译程序时,使用数学sin()
函数的相同C代码运行速度要慢得多。我正在使用-O2 -Wall -Wextra -mfpu=neon -mtune=cortex-a9 -march=armv7 -std=c++11
进行编译,而我的gcc是gcc (Ubuntu/Linaro 4.8.2-19ubuntu1) 4.8.2
。
我不认为sin()对于实时操作来说不是那么快,而且我知道对于更快的sin函数的一个好的折衷方案是使用查找表,但我在这里遇到的可能是编译器中的异常或错误,因为运行sin()函数确实需要很长时间。
我的程序在启动时创建了一些wavetables,虽然它几乎立即在Windows上启动,但在Linux / ARM上启动大约需要25-30秒......
以下是一些代码,显示了使用sinf()
函数减慢所有内容的位置。
for (int n = 0; n < 73; ++n)
{
// Max number of harmonics
int hrm = int(16000.f / twf[n]);
// Set vectors
basic_wf.assign(wavelength[n], 0);
for (int i = 0; i < wavelength[n]; ++i)
{
// Add harmonics
for (int h = 1; h < hrm; ++h)
{
const float harm = 0.14f * (sinf((float)i * FACTOR * twf[n] * (float)h) / (float)h);
if (h % 2 == 0) basic_wf[i] -= harm; // add even negative harmonic
else basic_wf[i] += harm; // add odd positive harmonic
}
}
}
这里我用73个表填充锯齿波形,为每个频率添加所需的谐波次数。音符的音高越低,谐波的数量越多(实际的sin()计算)。这几乎立即在Windows上运行......在我的Linux机器上花了一辈子的时间。
答案 0 :(得分:1)
代码建议,并且您在评论中的分析证实,sinf()
的论证的幅度可以变得非常大,当然可以达到几千。在trig函数的公共库实现中使用的精确参数减少可能是计算密集型的,因此对于大型参数来说很慢,尤其是当硬件平台不支持融合乘法加法运算时。这可能是您观察到的sinf()
性能低的一个因素。
您在评论中提到sinf()
的操作数包含因子π。这表明你实际上想要使用sinpif()
,其中sinpi(x)= sin(x *π)。 sinpi函数是在IEEE-754(2008)浮点标准中引入的,但尚未将其纳入语言标准。然而,一些工具链将其作为扩展。 sinpi()
的优点在于,无论参数的大小如何,它都只需要非常简单的参数减少,这可以大大减少执行时间。这导致性能提高。由于π的乘法是隐含的,因此它可以使用sinf()
提供比离散方法更高的精度。
我正在展示下面sinpif()
的示例C99实现。请注意,此代码在很大程度上依赖于标准数学函数fmaf()
来实现高处理速度和出色的准确性。如果您的CPU没有硬件支持融合乘法 - 加法(FMA)操作,则此函数执行速度非常慢,因为正确模拟fmaf()
并非易事。由于代码是以模块化方式编写的,因此您需要将编译器配置为应用最大量的函数内联,或者为所有组成函数添加适当的内联属性。
当您指出您的硬件平台不提供对FMA的本机支持时,您可以将fmaf(a,b,c)
替换为(a*b+c)
,但准确性会有所降低。根据我的测试,最大ulp误差增加到1.71364 ulps。这仍然非常好,但在这种情况下my_sinf()
不再被忠实地舍入,这通常被认为是理想的财产。
/* Argument reduction for sinpi, cospi, sincospi. Reduces to [-0.25, +0.25] */
float trig_red_pi_f (float a, int *i)
{
float r;
r = rintf (a + a);
*i = (int)r;
r = a - 0.5f * r;
return r;
}
/* Approximate cos(pi*x) for x in [-0.25,0.25]. Maximum ulp error = 0.87440 */
float cospif_poly (float s)
{
float r;
r = 0x1.d98dcep-3f; // 2.31227502e-1f
r = fmaf (r, s, -0x1.55c4e8p+0f); // -1.33503580e+0f
r = fmaf (r, s, 0x1.03c1d4p+2f); // 4.05870533e+0f
r = fmaf (r, s, -0x1.3bd3ccp+2f); // -4.93480206e+0f
r = fmaf (r, s, 0x1.000000p+0f); // 1.00000000e+0f
return r;
}
/* Approximate sin(pi*x) for x in [-0.25,0.25]. Maximum ulp error = 0.96441 */
float sinpif_poly (float a, float s)
{
float r;
r = -0x1.2dc6f8p-1f; // -5.89408636e-1f
r = fmaf (r, s, 0x1.46602ep+1f); // 2.54981017e+0f
r = fmaf (r, s, -0x1.4abbc0p+2f); // -5.16770935e+0f
r = r * s;
r = fmaf (r, a, -0x1.777a5cp-24f * a); // PI_lo // -8.74227766e-8f
r = fmaf (a, 0x1.921fb6p+1f, r); // PI_hi // 3.14159274e+0f
return r;
}
/* Compute sin(pi*x) and cos(pi*x) based on quadrant */
float sinpif_cospif_core (float a, int i)
{
float r, s;
s = a * a;
r = (i & 1) ? cospif_poly (s) : sinpif_poly (a, s);
if (i & 2) {
r = 0.0f - r; // don't change "sign" of NaNs or create negative zeros
}
return r;
}
/* maximum ulp error = 0.96411 */
float my_sinpif (float a)
{
float r;
int i;
r = trig_red_pi_f (a, &i);
r = sinpif_cospif_core (r, i);
/* IEEE-754: sinPi(+n) is +0 and sinPi(-n) is -0 for positive integers n */
r = (a == truncf (a)) ? (a * 0.0f) : r;
return r;
}
答案 1 :(得分:0)
你可以做Napier和Co.为计算对数表所做的事情 - 或者更准确地说是1.000001
或类似力量的表格。
如果您需要值为sin(k*w)
的向量,则计算c1000=cos(1000*w)
和s1000=sin(1000*w)
,设置
c[0] = 1; s[0] = 0;
c[1000]=c1000; s[1000] = s1000;
然后迭代
c[1000*(k+1)] = c1000*c[1000*k]-s1000*s[1000*k];
s[1000*(k+1)] = c1000*s[1000*k]+s1000*c[1000*k];
然后使用三角形标识再次使用c1=cos(w)
和s1=sin(w)
填充空白,向前迈出1000步,或者如果你想要500前锋和500后退。这种多级方法应该使浮点误差的积累足够小。
On&#34; big&#34;处理器应该没有大的差别,2次乘法和sincos评估的成本应该是可比较的。在你的情况下,乘法过程应该有一些优势。