【问题标题】:fast small angle sinus/cosinus approximation快速小角度正弦/余弦逼近
【发布时间】:2013-08-09 12:15:25
【问题描述】:

我正在做一些刚体旋转动力学模拟,这意味着我必须计算许多小角度的旋转,这在三角函数的评估中存在性能瓶颈。现在我按 Taylor(McLaurin) 系列来做:

class double2{
  double x,y;
  // Intristic full sin/cos 
  final void rotate   ( double a){ 
     double x_=x; 
     double ca=Math.cos(a); double sa=Math.sin(a); 
     x=ca*x_-sa*y; y=sa*x_+ca*y; 
  }
  // Taylor 7th-order aproximation
  final void rotate_d7( double a){ 
     double x_=x;
     double a2=a*a;
     double a4=a2*a2;
     double a6=a4*a2;
     double ca= 1.0d - a2  /2.0d + a4  /24.0d  - a6/720.0d;
     double sa=   a  - a2*a/6.0d + a4*a/120.0d - a6*a/5040.0d; 
     x=ca*x_-sa*y; y=sa*x_+ca*y; 
  }
}

但性能速度的交易并不像我预期的那么好:

                     error(100x dphi=Pi/100 )    time [ns pre rotation]
  v.rotate_d1()   :  -0.010044860504615213    9.314306 ns/op 
  v.rotate_d3()   :   3.2624666136960023E-6  16.268745 ns/op 
  v.rotate_d5()   :  -4.600003294941146E-10  35.433617 ns/op 
  v.rotate_d7()   :   3.416711358283919E-14  49.831547 ns/op 
  v.rotate()      :   3.469446951953614E-16  75.70213  ns/op 

有没有更快的方法来评估小角度的 sin() 和 cos() 的近似值(例如

我在想可能是一些有理数列,还是连续分数近似?你知道任何? (预计算表在这里没有意义)

【问题讨论】:

  • 有什么理由要使用系列而不是 sincos() 函数吗?您能否说明您使用的是什么语言?
  • 为什么预计算表在您的情况下没有意义?内存限制?降低精度会限制您需要存储的值的数量,因为角度
  • 看看CORDIC
  • 您在什么语言和平台上运行?这会有很大的不同。 @Sven - 我怀疑用软件编码的 CORDIC 会很快 - 尽管它肯定在 FPGA 上运行。

标签: performance trigonometry approximation


【解决方案1】:

您可能会发现调整计算可以提高性能。例如:

const double c7 = -1/5040d;
const double c5 = 1/120d;
const double c3 = -1/6d;

double a2 = a * a;

double sa = (((c7 * a2 + c5) * a2 + c3) * a2 + 1) * a;
// similarly for cos

现在优化器可能会自己做一些事情,所以你的里程可能会有所不同。有兴趣知道这两种方法的结果。

【讨论】:

  • 是的,永远不要除以常数。浮点的严格规则可能会阻止编译器进行代数操作,尽管 -flags 可能允许这样做。多项式的这种表示也很好,因为它减少了运算次数并将其分解为 MAC、MAC、MAC、MUL。
  • 好的,我得到了 2 倍的加速。
【解决方案2】:

不要优化三角函数,看看你是否可以不用它们。刚体模拟往往非常适合矢量数学。

【讨论】:

  • 是的,我正在使用矢量数学(或者甚至更好的 2D 复数和 3D 中的四元数)但我仍然不想超越一阶扩展(使时间步长更长并保持高精确)。而精确解是由正弦和余弦组成的旋转矩阵。因此,最好将精确解扩展到更高阶的泰勒(或其他)级数。
【解决方案3】:

两种方法:尽可能降低精度(在视频游戏中经常使用,如果您以性能为目标,则使用可接受的最低精度)

您应该尝试使用列表值。每次执行一次(当游戏加载时?)计算一个 sinus/ cosinus/ 数组,然后您可以在恒定时间内访问。

float cosAlpha = COSINUS[(int)(k*alpha)]; // e.g: k = 1000 

调整 k 和数组大小以选择角度分辨率与内存占用。

编辑:不要忘记使用 cosinus/sinus 函数的奇偶校验以避免选项卡中的重复值 编辑2:尝试浮动而不是双倍。差异对玩家来说是微不足道的,而影响性能的方式很有趣。测试一下!

【讨论】:

  • 这是一种非常老派的优化技术计算。测量。
  • 我不想对整个周期进行非常粗略的近似。我想要小角度的非常精确的近似值。我想提高一次运动迭代的精度(通过角度 dphi = Omega*dt 的一些小微分旋转矢量)
【解决方案4】:

你能添加一些内联汇编器吗?以 i386 'fsincos' 指令为目标可能是最快的方法:

Vector2 unit_vector ( Angle angle ) {
  Vector2 r;

//now the normal processor detection
//and various platform specific vesions

#  if defined (__i386__) && !defined (NO_ASM)
#    if defined __GNUC__
#      define ASM_SINCOS
      asm ("fsincos" : "=t" (r.x), "=u" (r.y) : "0" (angle.radians()));

#    elif defined _MSC_VER
#      define ASM_SINCOS
      double a = angle.radians();
      __asm fld a
      __asm fsincos
      __asm fstp r.x
      __asm fstp r.y
#    endif
#  endif
}

来自here。 这具有在一次调用中计算 sin 和 cos 的额外好处。

编辑:它是 Java。

您的轮换是否适当地自给自足,可以通过 JNI 一次卸载数千个?否则这种特定于硬件的方法不好。

【讨论】:

    【解决方案5】:

    对于较小的 x(x

    最大偏差为0.0013。

    【讨论】:

      猜你喜欢
      • 2015-06-15
      • 1970-01-01
      • 2017-06-06
      • 1970-01-01
      • 2013-04-05
      • 1970-01-01
      • 2018-09-10
      • 2017-10-06
      • 1970-01-01
      相关资源
      最近更新 更多