C# 使用预先计算的平移数组快速计算正弦/余弦

C# 使用预先计算的平移数组快速计算正弦/余弦,c#,performance,optimization,C#,Performance,Optimization,下面的代码使用预先计算的内存表执行Sin/Cos函数。在下面的示例中,该表有1024*128项,涵盖了从0到2pi的所有Sin/Cos值。我知道我可以使用Sin/Cos对称,只保留1/4的值,但在计算值时,我会有更多的“if” private const double PI2 = Math.PI * 2.0; private const int TABLE_SIZE = 1024 * 128; private const double TABLE_SIZE_D = (double)TABLE_

下面的代码使用预先计算的内存表执行Sin/Cos函数。在下面的示例中,该表有1024*128项,涵盖了从0到2pi的所有Sin/Cos值。我知道我可以使用Sin/Cos对称,只保留1/4的值,但在计算值时,我会有更多的“if”

private const double PI2 = Math.PI * 2.0; 
private const int TABLE_SIZE = 1024 * 128;
private const double TABLE_SIZE_D = (double)TABLE_SIZE;
private const double FACTOR = TABLE_SIZE_D / PI2;

private static double[] _CosineDoubleTable;
private static double[] _SineDoubleTable;
设置翻译表

private static void InitializeTrigonometricTables(){
   _CosineDoubleTable = new double[TABLE_SIZE];
   _SineDoubleTable = new double[TABLE_SIZE];

   for (int i = 0; i < TABLE_SIZE; i++){
      double Angle = ((double)i / TABLE_SIZE_D) * PI2;
      _SineDoubleTable[i] = Math.Sin(Angle);
      _CosineDoubleTable[i] = Math.Cos(Angle);
   }
}
private static void InitializeTrigonometricTables(){
_CosineDoubleTable=新的双精度[表格大小];
_SineDoubleTable=新的双精度[表格大小];
对于(int i=0;i
该值是弧度的两倍

Value %= PI2;  // In case that the angle is larger than 2pi
if (Value < 0) Value += PI2; // in case that the angle is negative
int index = (int)(Value * FACTOR); //from radians to index and casted in to an int
double sineValue = _SineDoubleTable[index]; // get the value from the table
Value%=PI2;//如果角度大于2pi
如果(值<0)值+=PI2;//如果角度为负
int索引=(int)(值*因子)//从弧度到索引并转换为整数
double sineValue=_SineDoubleTable[索引];//从表中获取值

我正在寻找一种更快的方法。以上4行占整个过程的约25%(执行了数十亿次)。

如果要计算这么多次

  • 使用处理器特定的数学库,如or和
  • 按组(向量)计算值
  • 当需要两者时,始终同时计算一个值的sin和cos
  • 检查您的算法复杂性和实现设计
  • 确保您使用的是所有处理器必须提供的-x64架构,以及任何有帮助的向量指令

  • 这将是相当他妈的快,因为它是

    <>如果你真的需要从这个代码中挤出每一个可以想象的性能下降,你可能会考虑在C++ DLL(甚至ASM)中编写它的这一部分(包括循环数十亿次的外循环)。确保您的编译器设置为允许您使用尽可能大的指令集


    [编辑]我忽略了表的大小-由于缓存未命中,这可能会大大降低代码的速度。您是否尝试过将其与Math.Cos()或其他近似trig函数的方法进行基准测试(您可以通过使用一些简单的乘法获得非常好的近似值)

    这看起来相当不错,除了mod操作。你能没有它吗

    如果值接近零,则可以使用

    while(Value > PI2) Value -= PI2;
    while(Value < 0) Value += PI2;
    
    while(Value>PI2)Value-=PI2;
    而(值<0)值+=PI2;
    

    或者,首先将索引强制转换为整数(可能超出范围),然后将其修改为整数可能会更快。如果表大小是2的倍数,您甚至可以使用位运算(如果编译器还没有这样做)。

    不能保证它会有很多好处,但取决于处理器,整数运算通常比浮点运算快。在这种情况下,我可能会重新排列前三行,首先计算一个整数,然后缩小其范围(如果需要)。当然,正如BlueRaja指出的,使用C++也几乎肯定会有帮助。使用汇编语言可能不会有什么好处,但是对于这样的表查找,C++编译器通常可以产生相当好的代码。 如果可能的话,我也会仔细研究你的精度要求——不知道你在用这些值做什么,很难说,但出于很多目的,你的表大小和你存储的精度远远超出了必要的范围,甚至接近于有用的范围


    最后,我要指出,至少值得研究一下整个战略是否值得。曾经,毫无疑问,使用表格来避免复杂的计算是一种可靠的策略。不过,处理器的速度比内存快得多,以至于现在这样的表查找通常是一种净损失。事实上,表几乎唯一有可能存在的方法是它是否足够小,可以放入处理器缓存。

    可以尝试的一件事是使用cos(x)=sin(x+pi/2)。将正弦表放大四分之一,这样就可以将其重新用作从四分之一开始的余弦表。不确定C是否允许像C一样获取指向表中间的指针。但即使不是这样,减少的缓存使用量也可能比将偏移量添加到正弦表中所需的时间更有价值

    也就是说,用C表示:

    double* _CosineDoubleTable = &_SineDoubleTable[TABLESIZE / 4];
    

    我假设泰勒展开式对你没用。因此,如果要使用表: 你只需要一张一半大的桌子

  • cos(x)=sin(pi/2-x)。
  • sin(pi+x)=-sin(x)
  • 您可以使您的代码无分支。 首先转换为int格式

    int index = (int)(Value * FACTOR);
    index %= TABLE_SIZE; // one instuction (mask)
    index = (index >= 0) ? index :TABLE_SIZE-index; // one instruction isel
    double sineValue = _SineDoubleTable[index];
    

    和数学相比,反正是罪。配置文件配置文件。(在实际示例中,缓存未命中可能会降低代码的速度。)

    您可以尝试使用不安全的代码来消除数组边界检查。
    但即使是一个不安全的、优化的版本似乎也离我们不远

    基于随机值的1'000'000'000次迭代的结果:

    (1) 00:00:57.3382769  // original version
    (2) 00:00:31.9445928  // optimized version
    (3) 00:00:21.3566399  // Math.Sin
    
    代码:

    静态双原点(双值)
    {
    值%=PI2;
    如果(值<0)值+=PI2;
    int索引=(int)(值*因子);
    返回表[索引];
    }
    静态不安全双正弦优化(双*正弦双表,双值)
    {
    int索引=(int)(值*因子)%表大小;
    返回(索引<0)?SineDoubleTable[索引+表格大小]
    :表[索引];
    }
    
    测试程序:

    InitializeTrigonometricTables();
    Random random = new Random();
    
    SinOriginal(random.NextDouble());
    var sw = System.Diagnostics.Stopwatch.StartNew();
    for (long i = 0; i < 1000000000L; i++)
    {
        SinOriginal(random.NextDouble());
    }
    sw.Stop();
    Console.WriteLine("(1) {0}  // original version", sw.Elapsed);
    
    fixed (double* SineDoubleTable = _SineDoubleTable)
    {
        SinOptimized(SineDoubleTable, random.NextDouble());
        sw = System.Diagnostics.Stopwatch.StartNew();
        for (long i = 0; i < 1000000000L; i++)
        {
            SinOptimized(SineDoubleTable, random.NextDouble());
        }
        sw.Stop();
        Console.WriteLine("(2) {0}  // optimized version", sw.Elapsed);
    }
    
    Math.Sin(random.NextDouble());
    sw = System.Diagnostics.Stopwatch.StartNew();
    for (long i = 0; i < 1000000000L; i++)
    {
        Math.Sin(random.NextDouble());
    }
    sw.Stop();
    Console.WriteLine("(3) {0}  // Math.Sin", sw.Elapsed);
    
    InitializeTrigonometricTables();
    随机=新随机();
    SinOriginal(random.NextDouble());
    var sw=System.Diagnostics.Stopwatch.StartNew();
    对于(长i=0;i<1000000000L;i++)
    {
    SinOriginal(random.NextDouble());
    }
    sw.Stop();
    Console.WriteLine(“(1){0}//原始版本”,sw.appeased);
    固定(双*SineDoubleTable=\u SineDoubleTable)
    {
    SINOOptimized(SineDoubleTable,random.NextDouble)(
    
    InitializeTrigonometricTables();
    Random random = new Random();
    
    SinOriginal(random.NextDouble());
    var sw = System.Diagnostics.Stopwatch.StartNew();
    for (long i = 0; i < 1000000000L; i++)
    {
        SinOriginal(random.NextDouble());
    }
    sw.Stop();
    Console.WriteLine("(1) {0}  // original version", sw.Elapsed);
    
    fixed (double* SineDoubleTable = _SineDoubleTable)
    {
        SinOptimized(SineDoubleTable, random.NextDouble());
        sw = System.Diagnostics.Stopwatch.StartNew();
        for (long i = 0; i < 1000000000L; i++)
        {
            SinOptimized(SineDoubleTable, random.NextDouble());
        }
        sw.Stop();
        Console.WriteLine("(2) {0}  // optimized version", sw.Elapsed);
    }
    
    Math.Sin(random.NextDouble());
    sw = System.Diagnostics.Stopwatch.StartNew();
    for (long i = 0; i < 1000000000L; i++)
    {
        Math.Sin(random.NextDouble());
    }
    sw.Stop();
    Console.WriteLine("(3) {0}  // Math.Sin", sw.Elapsed);