假设您必须计算域在0.01到360.01之间的正弦(余弦或正切 - 无论如何)。 (使用C#)
什么会更高效?
我认为,鉴于域名,选项2会更快。在域精度(0.0000n)的什么时刻,计算的性能超过了查找。
答案 0 :(得分:20)
更新:直读到最后。看起来查找表比Math.Sin更快。
我猜测查找方法会比Math.Sin更快。我还会说它会更快很多,但罗伯特的答案让我觉得我仍然希望对此进行基准测试以确定。我做了很多音频缓冲处理,我注意到了这样的方法:
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的速度至少是查找表的两倍< / strong>(使用字典)。这是使用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执行得更差。此外,这个结果更有利于使用Math.Sin,因为SinBuddy.Sin进行乘法以将度数转换为每次调用时的弧度角度,而SinBuddy.SinLookup只进行直接查找。
这是在便宜的笔记本电脑上(没有双核或任何花哨的东西)。罗伯特,你这个男人! (但我仍然认为我应该接受检查,因为我做了工作)。
更新2 :好的,我是个白痴......事实证明停止并重新启动秒表并没有重置经过的毫秒数,所以查找只有一半快,因为时间是包括Math.Sin调用的时间。此外,我重读了这个问题,并意识到你正在谈论在一个简单的数组中缓存值,而不是使用Dictionary。这是我修改后的代码(我将旧代码留作后代的警告):
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 快一点,但不是很多。
请自行验证这些结果,因为我已经证明了我的无能。
答案 1 :(得分:15)
过去,数组查找是执行快速触发计算的一个很好的优化。
但是对于缓存命中,内置数学协处理器(使用表查找)和其他性能改进,最好自己计算特定代码以确定哪些代码会更好。
答案 2 :(得分:7)
对于性能问题,唯一正确的答案是您在测试后达到的答案。但是,在测试之前,您需要确定测试的工作是否值得花时间 - 这意味着您已经确定了性能问题。
如果您只是好奇,可以轻松编写测试来比较速度。但是,您需要记住,使用查找表的内存可能会影响较大应用程序中的分页。因此,即使在小测试中分页速度更快,也可能会在使用更多内存的较大应用中减慢速度。
答案 3 :(得分:2)
这个问题的答案完全取决于查找表中有多少个值。你说“域名介于0.01和360.01之间”,但是你没有说明该范围内可以使用多少个值,或者你需要答案的准确程度。原谅我不希望看到重要的用于在非科学背景下传达隐含意义的数字。
仍然需要更多信息来回答这个问题。 0.01到360.01之间的预期值分布是多少?您是否正在处理除简单sin()计算之外的大量数据?
36000双精度值在内存中占用超过256k;查找表太大,无法容纳大多数机器上的L1缓存;如果你直接穿过桌子,你会在每个sizeof(cacheline)/ sizeof(双)访问时错过L1,并且可能会命中L2。另一方面,如果您的表访问或多或少是随机的,那么几乎每次进行查找时都会丢失L1。
这也很大程度上取决于您所使用的平台的数学库。例如,sin函数的常见i386实现范围从大约40个周期到400个周期甚至更多,具体取决于您的确切微体系结构和库供应商。我没有计时微软库,所以我不知道C#Math.sin实现的确切位置。
由于来自L2的负载通常比理智平台上的40个周期快,因此可以合理地期望查找表可以更快地被隔离考虑。但是,我怀疑你是在孤立地计算罪恶();如果sin()的参数跳到表中,那么你将从缓存中将计算的其他步骤所需的其他数据吹掉;虽然sin()计算变得更快,但计算其他部分的减速可能超过加速。只有仔细的测量才能真正回答这个问题。
我是否可以从您的其他评论中了解到,您正在将此作为FFT计算的一部分?您是否有理由需要使用自己的FFT而不是使用已经存在的众多极高质量的实现之一?
答案 4 :(得分:2)
由于您提到傅里叶变换作为应用程序,您可能还会考虑使用方程计算您的正弦/余弦
sin(x + y)= sin(x)cos(y)+ cos(x)sin(y)
cos(x + y)= cos(x)cos(y) - sin(x)sin(y)
即。你可以从sin((n-1)* x),cos((n-1)* x)迭代计算n = 0,1,2 ...的sin(n * x),cos(n * x)和常数sin(x),cos(x)有4次乘法。 当然,只有在必须对算术序列求值sin(x),cos(x)时才有效。
比较没有实际实施的方法很困难。这很大程度上取决于您的表格在缓存中的适用程度。
答案 5 :(得分:1)
Math.Sin更快。写作的人很聪明,并且在准确和快速时使用表格查找,并在更快的时候使用数学。并且没有任何关于该领域使其特别快,大多数trig函数实现的第一件事是无论如何映射到有利的域。
答案 6 :(得分:0)
由于您的查找表中可能有数千个值,您可能要做的是使用字典,当您计算值时,将其放入字典中,这样您只需计算一次值,并使用C#函数进行计算。
但是,没有理由一遍又一遍地重新计算相同的值。