我有以下代码使用预先计算的内存表执行Sin / Cos函数。在下面的示例中,该表具有1024 * 128项,涵盖从0到2pi的所有Sin / Cos值。我知道我可以使用Sin / Cos对称并只保留1/4的值但是在计算值时我会有更多'ifs'。
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);
}
}
价值是弧度的两倍。
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
我正在寻找更快的方法来做到这一点。以上4行是整个过程的约25%(执行数十亿次)。
答案 0 :(得分:21)
您可以尝试使用不安全的代码来消除数组边界检查 但即使是不安全的优化版本似乎也不会出现在Math.Sin附近。
基于具有随机值的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
代码:
static double SinOriginal(double Value)
{
Value %= PI2;
if (Value < 0) Value += PI2;
int index = (int)(Value * FACTOR);
return _SineDoubleTable[index];
}
static unsafe double SinOptimized(double* SineDoubleTable, double Value)
{
int index = (int)(Value * FACTOR) % TABLE_SIZE;
return (index < 0) ? SineDoubleTable[index + TABLE_SIZE]
: SineDoubleTable[index];
}
测试程序:
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);
答案 1 :(得分:8)
我假设泰勒的扩张对你没用。所以如果你想使用一个表: 你只需要一张大一半的桌子。
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];
无论如何要与Math.Sin比较。简介简介Priofile。 (在实际示例中,缓存未命中可能会降低代码速度。)
答案 2 :(得分:4)
如果你需要多次计算,
答案 3 :(得分:2)
除了mod操作外,这看起来还不错。你能没有吗?
如果值接近零,则可以使用
while(Value > PI2) Value -= PI2;
while(Value < 0) Value += PI2;
或者首先将索引转换为整数(可能超出范围)可能会更快,然后将其修改为整数。如果表大小将是2的倍数,您甚至可以使用位操作(如果编译器不已执行此操作)。
答案 4 :(得分:2)
不能保证它会带来很多好处,但是根据你的处理器,整数数学通常比浮点数学更快。在这种情况下,我可能会重新排列前三行,先计算一个整数,然后减小其范围(如果需要)。当然,正如BlueRaja指出的那样,使用C ++几乎肯定也会有所帮助。使用汇编语言可能不会有太大的好处 - 对于像这样的表查找,C ++编译器通常可以生成相当好的代码。
如果可能的话,我也会非常努力地看待你的准确性要求 - 不知道你对这些价值做了什么,很难说,但是对于很多的目的,你的表格大小和您存储的精度远超出了必要或甚至接近有用的范围。
最后,我注意到至少值得研究整个策略是否值得。毫无疑问,使用表来避免复杂计算是一个可靠的策略。尽管如此,处理器加快了 lot 的速度 - 到目前为止,这样的表查找通常是净损失。事实上,几乎唯一的方法是表格是否足够小,以适应处理器缓存。
答案 5 :(得分:0)
这将是非常快的。
如果你真的需要从这段代码中挤出所有可以想象的性能下降,你可能要考虑在C ++ dll(甚至是ASM)中编写它的这一部分(包括循环数十亿次的循环)。确保您的编译器设置为允许您使用最大可能的指令集。
[编辑] 我错过了表的大小 - 由于缓存未命中,这可能会大大减慢您的代码速度。您是否尝试过针对Math.Cos()或其他近似trig函数的方法进行基准测试(使用Taylor Series可以通过一些简单的乘法获得非常好的近似值)
答案 6 :(得分:0)
你可以尝试的一件事是使用cos(x)= sin(x + pi / 2)这一事实。并使正弦表大四分之一,因此您可以将其重新用作从四分之一开始的余弦表。不确定C#是否允许您获得指向表格中间的指针,如C所示。但即使没有,减少的缓存使用量可能比增加到正弦表的偏移量的时间更多。
那,用C表示:
double* _CosineDoubleTable = &_SineDoubleTable[TABLESIZE / 4];