1. 使用Math.Sin
2. 使用预先计算好的查找数组
鉴于给定的定义域,我预计选项2会更快。在定义域的精度达到多少(0.0000n)时,计算的性能超过了查找数组的性能。
更新:请读完全文。看起来查找表比Math.Sin更快。
我猜查找表方法会比Math.Sin更快。我还要说它会快很多,但是Robert的回答让我想要对此进行基准测试,以确保。我做了很多音频缓冲处理,我注意到这样的方法:
for (int i = 0; i < audiodata.Length; i++)
{
audiodata[i] *= 0.5;
}
将会比执行速度快得多。
for (int i = 0; i < audiodata.Length; i++)
{
audiodata[i] = Math.Sin(audiodata[i]);
}
如果Math.Sin和简单乘法之间的差异很大,我会猜测Math.Sin和查找表之间的差异也很大。
不过,我不确定,我的带有Visual Studio的计算机在地下室,而且我太累了,不想花2分钟去确认这一点。
更新: 好吧,测试这个需要超过2分钟(20分钟左右),但看起来Math.Sin至少比使用查找表(使用Dictionary)快两倍。以下是使用Math.Sin或查找表执行Sin函数的类:
public class SinBuddy
{
private Dictionary<double, double> _cachedSins
= new Dictionary<double, double>();
private const double _cacheStep = 0.01;
private double _factor = Math.PI / 180.0;
public SinBuddy()
{
for (double angleDegrees = 0; angleDegrees <= 360.0;
angleDegrees += _cacheStep)
{
double angleRadians = angleDegrees * _factor;
_cachedSins.Add(angleDegrees, Math.Sin(angleRadians));
}
}
public double CacheStep
{
get
{
return _cacheStep;
}
}
public double SinLookup(double angleDegrees)
{
double value;
if (_cachedSins.TryGetValue(angleDegrees, out value))
{
return value;
}
else
{
throw new ArgumentException(
String.Format("No cached Sin value for {0} degrees",
angleDegrees));
}
}
public double Sin(double angleDegrees)
{
double angleRadians = angleDegrees * _factor;
return Math.Sin(angleRadians);
}
}
以下是测试/计时代码:
SinBuddy buddy = new SinBuddy();
System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();
int loops = 200;
// Math.Sin
timer.Start();
for (int i = 0; i < loops; i++)
{
for (double angleDegrees = 0; angleDegrees <= 360.0;
angleDegrees += buddy.CacheStep)
{
double d = buddy.Sin(angleDegrees);
}
}
timer.Stop();
MessageBox.Show(timer.ElapsedMilliseconds.ToString());
// lookup
timer.Start();
for (int i = 0; i < loops; i++)
{
for (double angleDegrees = 0; angleDegrees <= 360.0;
angleDegrees += buddy.CacheStep)
{
double d = buddy.SinLookup(angleDegrees);
}
}
timer.Stop();
MessageBox.Show(timer.ElapsedMilliseconds.ToString());
使用0.01度的步长,循环200次(就像这段代码一样),使用Math.Sin大约需要1.4秒,而使用字典查找表则需要大约3.2秒。将步长降低到0.001或0.0001会使查找表的性能更差。此外,这个结果更有利于使用Math.Sin,因为SinBuddy.Sin在每次调用时都要进行角度从度数到弧度的乘法运算,而SinBuddy.SinLookup只是简单地查找。
这是在一台廉价笔记本电脑上(没有双核或任何花哨的东西)进行的测试。Robert,你是最棒的!(但我仍然认为我应该拿到支票,因为我做了这项工作)。
更新2:事实证明,停止并重新启动秒表不会重置经过的毫秒数,因此查找表似乎只快了一半,因为它的时间包括了Math.Sin调用的时间。此外,我重新阅读了问题,并意识到你所说的是将值缓存到一个简单的数组中,而不是使用字典。以下是我的修改后的代码(我将旧代码保留作为对未来人的警告):
public class SinBuddy
{
private Dictionary<double, double> _cachedSins
= new Dictionary<double, double>();
private const double _cacheStep = 0.01;
private double _factor = Math.PI / 180.0;
private double[] _arrayedSins;
public SinBuddy()
{
// set up dictionary
for (double angleDegrees = 0; angleDegrees <= 360.0;
angleDegrees += _cacheStep)
{
double angleRadians = angleDegrees * _factor;
_cachedSins.Add(angleDegrees, Math.Sin(angleRadians));
}
// set up array
int elements = (int)(360.0 / _cacheStep) + 1;
_arrayedSins = new double[elements];
int i = 0;
for (double angleDegrees = 0; angleDegrees <= 360.0;
angleDegrees += _cacheStep)
{
double angleRadians = angleDegrees * _factor;
//_cachedSins.Add(angleDegrees, Math.Sin(angleRadians));
_arrayedSins[i] = Math.Sin(angleRadians);
i++;
}
}
public double CacheStep
{
get
{
return _cacheStep;
}
}
public double SinArrayed(double angleDegrees)
{
int index = (int)(angleDegrees / _cacheStep);
return _arrayedSins[index];
}
public double SinLookup(double angleDegrees)
{
double value;
if (_cachedSins.TryGetValue(angleDegrees, out value))
{
return value;
}
else
{
throw new ArgumentException(
String.Format("No cached Sin value for {0} degrees",
angleDegrees));
}
}
public double Sin(double angleDegrees)
{
double angleRadians = angleDegrees * _factor;
return Math.Sin(angleRadians);
}
}
测试/定时代码:
SinBuddy buddy = new SinBuddy();
System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();
int loops = 200;
// Math.Sin
timer.Start();
for (int i = 0; i < loops; i++)
{
for (double angleDegrees = 0; angleDegrees <= 360.0;
angleDegrees += buddy.CacheStep)
{
double d = buddy.Sin(angleDegrees);
}
}
timer.Stop();
MessageBox.Show(timer.ElapsedMilliseconds.ToString());
// lookup
timer = new System.Diagnostics.Stopwatch();
timer.Start();
for (int i = 0; i < loops; i++)
{
for (double angleDegrees = 0; angleDegrees <= 360.0;
angleDegrees += buddy.CacheStep)
{
double d = buddy.SinLookup(angleDegrees);
}
}
timer.Stop();
MessageBox.Show(timer.ElapsedMilliseconds.ToString());
// arrayed
timer = new System.Diagnostics.Stopwatch();
timer.Start();
for (int i = 0; i < loops; i++)
{
for (double angleDegrees = 0; angleDegrees <= 360.0;
angleDegrees += buddy.CacheStep)
{
double d = buddy.SinArrayed(angleDegrees);
}
}
timer.Stop();
MessageBox.Show(timer.ElapsedMilliseconds.ToString());
这些结果非常不同。使用Math.Sin需要大约850毫秒,字典查找表需要大约1300毫秒,而基于数组的查找表需要大约600毫秒。所以看起来,一个(正确编写的[gulp])查找表实际上比使用Math.Sin要快一点,但差距不大。
请自行验证这些结果,因为我已经证明了我的无能。
过去,数组查找是快速三角函数计算的良好优化方式。
但是,随着缓存命中、内置数学协处理器(使用表查找)和其他性能改进的出现,最好自己测试特定代码来确定哪种方法执行更佳。
对于性能问题,唯一正确的答案是在测试之后得出的答案。但是,在测试之前,您需要确定测试的努力是否值得您的时间——这意味着您已经确定了性能问题。
如果您只是好奇,可以轻松编写一个测试来比较速度。但是,请记住,为查找表使用内存可能会影响更大应用程序中的分页。因此,即使在您的小型测试中分页更快,它也可能会减慢使用更多内存的更大应用程序的速度。
抱歉打扰,但有一个好的解决方案可以快速索引查找表:
https://jvm-gaming.org/t/fast-math-sin-cos-lookup-tables/36660这是用Java编写的,但只需要几分钟即可将其移植到C#。
我进行了测试,并在100000次迭代中得到了以下结果:
Math.Sin: 0.043 sec
Mathf.Sin: 0.06 sec (Unity`s Mathf lib)
MathTools.Sin: 0.026 (lookup tables static class).
可能在Java中,它会给出50倍的提升(或者在2011年确实是这样,但在2021年的C#中,差异只有大约2倍)。
Math.Sin更快。编写此函数的人很聪明,当准确度更高、速度更快时使用表格查找,而当速度更快时使用数学计算。此领域没有什么特别之处,大多数三角函数实现的第一件事就是将其映射到一个有利的领域。
由于您的查找表可能有数千个值,您可能希望拥有一个字典,在计算值时将其放入字典中,这样您只需要计算每个值一次,并使用C#函数进行计算。
但是,没有理由反复重新计算相同的值。