c# - 计算与查找表的正弦值性能?

标签 c# performance math signal-processing

假设您必须计算定义域介于 0.01 和 360.01 之间的正弦(余弦或正切 - 任意)。 (使用 C#)

什么会更高效?

  1. 使用 Math.Sin
  2. 使用具有预先计算值的查找数组

我预计在给定域的情况下,选项 2 会快得多。在域的精度 (0.0000n) 的什么点上,计算的性能超过了查找。

最佳答案

更新:通读到最后。看起来查找表毕竟比 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 至少比查找表快两倍(使用字典)。这是使用 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 调用的时间.另外,我重新阅读了这个问题,意识到你是在谈论将值缓存在一个简单的数组中,而不是使用字典。这是我修改后的代码(我保留旧代码作为对后代的警告):

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 快一点,但幅度不大。

请自行验证这些结果,因为我已经证明了我的无能。

关于c# - 计算与查找表的正弦值性能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1382322/

相关文章:

c# - 确保为特定队列扩展不超过 N 个实例

c# ProcessStartInfo.Start - 读取输出但超时

security - 什么是Android点密码系统的熵?

matlab - 涉及逆运算的矩阵乘法 : getting infinity

c++ - 计算所有可能的乘积为完全平方数对的有效算法

c# - 如何使用带有 Entity Framework Code First 的 Postgresql 的串行字段

c# - 如何在aes加密和解密算法中使用随机salt和随机iv?

java - SimpleXml @ElementListUnion 标签在 Android 上的性能

jquery - 粘性导航 jquery css 代码性能

JavaScript 代码改进