【问题标题】:Fastest Math.Max(value,0)最快的 Math.Max(value,0)
【发布时间】:2015-06-09 18:12:18
【问题描述】:

我有一个算法可以将浮点数/双精度数与 0 进行大量比较。

类似的东西:

var sum = 0.0;
for(int i=0;i<data.Length;i++)
  sum += Math.Max(data[i],0);

我想知道是否有更快的方法来做到这一点? 在我的微基准测试中,它的执行速度几乎没有提高 (5-7%)。

public static unsafe float FasterCompare(float value)
{
  var val = *(int*) &value;
  return val > 0 ? value : 0;
}

【问题讨论】:

  • 是什么让您认为这是需要优化的主要性能瓶颈?
  • @Michal 那么听起来你的时间最好花在其他地方。
  • referencesource.microsoft.com/#mscorlib/system/math.cs 请参阅第 441 行的 Math.Max,它并不过分复杂,但如果您可以保证没有任何值是 Single.Nan,那么您可以通过编写自己的检查来删除。归结为 sum += (data[i] > 0.0f ? data[i] : 0.0f)。还要删除隐式转换,因为这会花费一些东西,根据数组将 sum 声明为 floatdouble,现在您正在对浮点数求和,转换整数,然后再次转换为双精度...
  • 你到底想在这里做什么?您将数据中大于或等于零的任何值添加到总和中。为什么还要Math.Max()?您可以检查该值是否 >= 0 并添加它。
  • 您的基准测试存在严重问题,始终存在,此代码的成本是读取数组元素,而不是比较。访问内存是迄今为止处理器所做的最昂贵的事情。比较是免费的,然后处理器只是停止等待值。

标签: c# performance


【解决方案1】:

你当前的代码是这样的:

var sum = 0.0;
for(int i=0;i<data.Length;i++)
  sum += Math.Max(data[i],0);

Math.Max 正在这样做:

public static double Max(double val1, double val2) 
{
    if (val1 > val2)
        return val1;

    if (Double.IsNaN(val1))
        return val1;

    return val2;
}

如果您知道自己不会拥有 NaN,请在没有 NaN 检查的情况下实现自己的 Max

public static double Max(double val1, double val2) 
{
    if (val1 > val2)
        return val1;

    return val2;
}

【讨论】:

  • 这是我在真实代码中实际拥有的。我应该指定的。
【解决方案2】:

写一个小基准,这是我的结果:

你的版本:~2.3ms

优化版:~1.4ms

代码如下:

float[] data = new float[150000];
Random rnd = new Random(12345);

for (int i = 0; i < data.Length; i++)
{
    data[i] = (float)(rnd.NextDouble() * 5000.0 - 2500.0);
}

Stopwatch sw = new Stopwatch();

sw.Start();

var varsum = 0.0;  //varsum is a DOUBLE!!!!

for (int i = 0; i < data.Length; i++)
    varsum += Math.Max(data[i], 0);        //implicit conversions, float->double, int->float

sw.Stop();

Console.WriteLine("Varsum : " + varsum);

Console.WriteLine("Time it took for the original: " + sw.Elapsed.TotalMilliseconds + " ms");
   
float floatsum = 0.0f;
    
sw.Reset();
sw.Start();

floatsum = 0.0f;

for (int i = 0; i < data.Length; i++)
    if (data[i] > 0.0f)
        floatsum += data[i];

sw.Stop();

Console.WriteLine("OptimizedSum: " + floatsum);

Console.WriteLine("Time it took for \"optimized\" version: " + sw.Elapsed.TotalMilliseconds + " ms");

//Equality on floating point numbers doesn't work like this, but...
Console.WriteLine("Are these two equal? " + (floatsum == varsum).ToString());
Console.WriteLine("How close are they? " + Math.Abs(floatsum - varsum).ToString("00.0000000000000000"));

Console.ReadKey(true);

控制台应用程序,32 位进程,运行调试模式,为 AnyCPU 编译。第二个版本快 60% 左右。这可能是因为不必调用无法内联的方法。

还需要注意的是,这两个方法返回的值不同。这是因为 doublefloat 的精度不同。如果您正在使用浮点数,请继续使用浮点数。与双精度相同,不要来回转换。上面的例子给了我超过65的差异!

小提琴:https://dotnetfiddle.net/S5qmCg(小提琴版本不反映上述相同的时间值,我假设因为它的资源在服务器端有限,所以请在您自己的计算机上尝试)

【讨论】:

  • 我发现差异较小,但我可以确认优化版本在我的机器上的运行速度平均快了 15-20%。
  • 将其添加到运行 10 次的循环中(例如)让 JIT 优化,尝试将代码放入自己的方法中并调用几次。
  • 我刚刚完全使用了你的代码。不想再花时间在他试图做的这种毫无意义的优化上。这里不是说 O(nlogn) 或 O(n^2),只是 O(n)。
【解决方案3】:

取决于您的阵列(和您的机器)有多大。根据数组的大小,这在我的机器上要快得多:

using System;
using System.Diagnostics;
using System.Linq;

public class Program
{
    public static void Main()
    {
        float[] data = new float[1500000];
        Random rnd = new Random(12345);

        for (int i = 0; i < data.Length; i++)
        {
            data[i] = (float)(rnd.NextDouble() * 5000.0 - 2500.0);
        }

        Stopwatch sw = new Stopwatch();

        sw.Start();

        var varsum = 0.0f;  //varsum is a DOUBLE!!!! change this to 0.0f to make them equal!

        for (int i = 0; i < data.Length; i++)
            varsum += Math.Max(data[i], 0);        //implicit conversions, float->double, int->float

        sw.Stop();

        Console.WriteLine("Varsum : " + varsum);

        Console.WriteLine("Time it took for the original: " + sw.Elapsed.TotalMilliseconds + " ms");

        float floatsum = 0.0f;

        sw.Reset();
        sw.Start();

        floatsum = 0.0f;
        floatsum=data.AsParallel().Where(d=>d>0).Sum();

        sw.Stop();

        Console.WriteLine("OptimizedSum: " + floatsum);

        Console.WriteLine("Time it took for \"optimized\" version: " + sw.Elapsed.TotalMilliseconds + " ms");

        //Equality on floating point numbers doesn't work like this, but...
        Console.WriteLine("Are these two equal? " + (floatsum == varsum).ToString());
        Console.WriteLine("How close are they? " + Math.Abs(floatsum - varsum).ToString("00.0000000000000000"));

    }
}

【讨论】:

    猜你喜欢
    • 2016-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-07
    • 2017-01-02
    • 1970-01-01
    相关资源
    最近更新 更多