C:使用沉重的sin()来提高功能的性能

时间:2013-12-31 01:22:47

标签: c performance math optimization trigonometry

我有一个C函数,它根据经过的时间计算4个正弦值。使用gprof,我认为这个函数使用100%(100.7%确切地说是lol)的CPU时间。

void
update_sines(void)
{
    clock_gettime(CLOCK_MONOTONIC, &spec);
    s = spec.tv_sec;
    ms = spec.tv_nsec * 0.0000001;
    etime = concatenate((long)s, ms);

    int k;
    for (k = 0; k < 799; ++k)
    {
        double A1 = 145 * sin((RAND1 * k + etime) * 0.00333) + RAND5;           // Amplitude
        double A2 = 100 * sin((RAND2 * k + etime) * 0.00333) + RAND4;           // Amplitude
        double A3 = 168 * sin((RAND3 * k + etime) * 0.00333) + RAND3;           // Amplitude
        double A4 = 136 * sin((RAND4 * k + etime) * 0.00333) + RAND2;           // Amplitude

        double B1 = 3 + RAND1 + (sin((RAND5 * k) * etime) * 0.00216);           // Period
        double B2 = 3 + RAND2 + (sin((RAND4 * k) * etime) * 0.002);         // Period
        double B3 = 3 + RAND3 + (sin((RAND3 * k) * etime) * 0.00245);           // Period
        double B4 = 3 + RAND4 + (sin((RAND2 * k) * etime) * 0.002);         // Period

        double x = k;                                   // Current x

        double C1 = 0.6 * etime;                            // X axis move
        double C2 = 0.9 * etime;                            // X axis move
        double C3 = 1.2 * etime;                            // X axis move
        double C4 = 0.8 * etime + 200;                          // X axis move

        double D1 = RAND1 + sin(RAND1 * x * 0.00166) * 4;               // Y axis move
        double D2 = RAND2 + sin(RAND2 * x * 0.002) * 4;                 // Y axis move
        double D3 = RAND3 + cos(RAND3 * x * 0.0025) * 4;                // Y axis move
        double D4 = RAND4 + sin(RAND4 * x * 0.002) * 4;                 // Y axis move

        sine1[k] = A1 * sin((B1 * x + C1) * 0.0025) + D1;
        sine2[k] = A2 * sin((B2 * x + C2) * 0.00333) + D2 + 100;
        sine3[k] = A3 * cos((B3 * x + C3) * 0.002) + D3 + 50;
        sine4[k] = A4 * sin((B4 * x + C4) * 0.00333) + D4 + 100;
    }

}

这是gprof的输出:

Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  Ts/call  Ts/call  name    
100.07      0.04     0.04  

我目前正在使用这个大约30-31 fps的帧速率。现在我认为这是一种更有效的方法。

正如您所注意到的,我已经将所有分区更改为乘法,但这对性能的影响非常小。

我怎样才能提高这个数学重函数的性能?

4 个答案:

答案 0 :(得分:11)

除了其他答案中给出的所有其他建议外,这里还有一个纯粹的算法优化。

在大多数情况下,您计算的格式为sin(k * a + b),其中ab是常量,k是循环变量。如果您还要计算cos(k * a + b),那么您可以使用2D rotation matrix来形成递归关系(以矩阵形式):

|cos(k*a + b)| = |cos(a)  -sin(a)| * |cos((k-1)*a + b)|
|sin(k*a + b)|   |sin(a)   cos(a)|   |sin((k-1)*a + b)|

换句话说,您可以根据上一次迭代的值计算当前迭代的值。因此,您只需要对k == 0执行完整的触发计算,但其余部分可以通过此重复计算(一旦计算出cos(a)sin(a),它们就是常量)。所以你消除了75%的trig函数调用(不清楚可以为最后一组trig调用拉取相同的技巧)。

答案 1 :(得分:4)

如果您不需要所有这些精确度,请为您需要的sin()值创建查找,因此如果1度足够,请使用double sin_lookup[360], etc..以及可能float sin_lookup[360]浮动精度就足够了。

另外,正如评论中所述,在Keith的某个点上,&#34;你也可以考虑在查找值之间使用线性插值,这样可以提供更好的准确性(合理连续的函数而不是阶梯函数) )性能成本相当低&#34;

编辑:还考虑将硬编码的A1,A2,A3,A4模式更改为大小为[4]的数组,并从0到3循环 - 应允许在许多平台上进行矢量化并允许parrellism而无需管理线程

EDIT2:一些代码和结果

(在C ++中编码只是为了在精度之间轻松进行比较,C中的计算结果相同)

class simple_trig
{
public:
        simple_trig(size_t prec) : precision(prec)
        {
                static const double PI=3.141592653589793;
                const double dprec=(double)prec;
                const double quotient=(2.0*PI)/dprec;
                rev_quotient=dprec/(2.0*PI);
                values.reserve(prec);

                for (int i=0; i < precision; ++i)
                {
                        values[i]=::sin(quotient*(double)i);
                }
        }

        double sin(double x) const
        {
                double cvt=x*rev_quotient;
                int index=(int)cvt;
                double delta=cvt-(double)index;
                int lookup1=index%precision;
                int lookup2=(index+1)%precision;
                return values[lookup1]*(1.0-delta)+values[lookup2]*delta;
        }

        double cos(double x) const
        {
                double cvt=x*rev_quotient;
                int index=(int)cvt;
                double delta=cvt-(double)index;
                int lookup1=(index+precision/4)%precision;
                int lookup2=(index+precision/4+1)%precision;
                return values[lookup1]*(1.0-delta)+values[lookup2]*delta;
        }

private:
        const size_t precision;
        double rev_quotient;
        std::vector<double> values;
};

示例低是100,Med是1000,高是10,000

X=0 Sin=0 Sin Low=0 Sin Med=0 Sin High=0
X=0 Cos=1 Cos Low=1 Cos Med=1 Cos High=1
X=0.5 Sin=0.479426 Sin Low=0.479389 Sin Med=0.479423 Sin High=0.479426
X=0.5 Cos=0.877583 Cos Low=0.877512 Cos Med=0.877578 Cos High=0.877583
X=1.33333 Sin=0.971938 Sin Low=0.971607 Sin Med=0.971935 Sin High=0.971938
X=1.33333 Cos=0.235238 Cos Low=0.235162 Cos Med=0.235237 Cos High=0.235238
X=2.25 Sin=0.778073 Sin Low=0.777834 Sin Med=0.778072 Sin High=0.778073
X=2.25 Cos=-0.628174 Cos Low=-0.627986 Cos Med=-0.628173 Cos High=-0.628174
X=3.2 Sin=-0.0583741 Sin Low=-0.0583689 Sin Med=-0.0583739 Sin High=-0.0583741
X=3.2 Cos=-0.998295 Cos Low=-0.998166 Cos Med=-0.998291 Cos High=-0.998295
X=4.16667 Sin=-0.854753 Sin Low=-0.854387 Sin Med=-0.854751 Sin High=-0.854753
X=4.16667 Cos=-0.519036 Cos Low=-0.518818 Cos Med=-0.519034 Cos High=-0.519036
X=5.14286 Sin=-0.90877 Sin Low=-0.908542 Sin Med=-0.908766 Sin High=-0.90877
X=5.14286 Cos=0.417296 Cos Low=0.417195 Cos Med=0.417294 Cos High=0.417296
X=6.125 Sin=-0.157526 Sin Low=-0.157449 Sin Med=-0.157526 Sin High=-0.157526
X=6.125 Cos=0.987515 Cos Low=0.987028 Cos Med=0.987512 Cos High=0.987515
X=7.11111 Sin=0.73653 Sin Low=0.736316 Sin Med=0.736527 Sin High=0.73653
X=7.11111 Cos=0.676405 Cos Low=0.676213 Cos Med=0.676403 Cos High=0.676405
X=8.1 Sin=0.96989 Sin Low=0.969741 Sin Med=0.969887 Sin High=0.96989
X=8.1 Cos=-0.243544 Cos Low=-0.24351 Cos Med=-0.243544 Cos High=-0.243544
X=9.09091 Sin=0.327701 Sin Low=0.327558 Sin Med=0.3277 Sin High=0.327701
X=9.09091 Cos=-0.944782 Cos Low=-0.944381 Cos Med=-0.944779 Cos High=-0.944782
X=10.0833 Sin=-0.611975 Sin Low=-0.611673 Sin Med=-0.611973 Sin High=-0.611975
X=10.0833 Cos=-0.790877 Cos Low=-0.790488 Cos Med=-0.790875 Cos High=-0.790877

答案 2 :(得分:1)

在我看来,sine1,sine2,sine3和sine4阵列完全独立于彼此。因此,您基本上为4个不依赖的数组运行单个for循环。

产生4个线程,每个1个,所以你有4个for循环同时运行。在多核机器上,这应该可以大大加快您的功能。事实上,它应该是一个完美的4倍加速(+ - ...)。

答案 3 :(得分:0)

实际上结合线程的使用(考虑到这与OpenMP)和使用表来犯罪是一个好主意。如果可能的话,使用float而不是double,并且根据平台的不同,你也可以使用simd指令,但是后者会不必使用线程。

干杯