快速整数ABS功能

时间:2011-05-24 17:03:00

标签: c# .net optimization

int X = a-b;
int d = Math.Abs(X);

我很确定.NET不会内联。那么,我会做if(),还是其他一些鲜为人知的技巧?

7 个答案:

答案 0 :(得分:18)

我做了一些性能测试,以确定除了标准Math.Abs​​之外是否可以节省时间。

执行所有这些2000000000次后的结果(i从-1000000000到+1000000000,所以没有溢出):

Math.Abs(i)                    5839 ms     Factor 1
i > 0 ? i : -i                 6395 ms     Factor 1.09
(i + (i >> 31)) ^ (i >> 31)    5053 ms     Factor 0.86

(这些数字因不同的运行而有所不同)

基本上你可以比Math.Abs略微提升,但没什么了不起的。

有了这一点,您可以节省Math.Abs​​所需的一些时间,但可读性严重受损。
使用简单的分支,您实际上可以更慢。总的来说,在我看来不值得。

在32位操作系统,Net 4.0,VS 2010,发布模式下运行的所有测试,不附带调试器。

以下是实际代码:

class Program
{
    public static int x; // public static field. 
                         // this way the JITer will not assume that it is  
                         // never used and optimize the wholeloop away
    static void Main()
    {
        // warm up
        for (int i = -1000000000; i < 1000000000; i++)
        {
            x = Math.Abs(i);
        }

        // start measuring
        Stopwatch watch = Stopwatch.StartNew();
        for (int i = -1000000000; i < 1000000000; i++)
        {
            x = Math.Abs(i);
        }
        Console.WriteLine(watch.ElapsedMilliseconds);

        // warm up
        for (int i = -1000000000; i < 1000000000; i++)
        {
            x = i > 0 ? i : -i;
        }

        // start measuring
        watch = Stopwatch.StartNew();
        for (int i = -1000000000; i < 1000000000; i++)
        {
            x = i > 0 ? i : -i;
        }
        Console.WriteLine(watch.ElapsedMilliseconds);

        // warm up
        for (int i = -1000000000; i < 1000000000; i++)
        {
            x = (i + (i >> 31)) ^ (i >> 31);
        }

        // start measuring
        watch = Stopwatch.StartNew();
        for (int i = -1000000000; i < 1000000000; i++)
        {
            x = (i + (i >> 31)) ^ (i >> 31);
        }
        Console.WriteLine(watch.ElapsedMilliseconds);


        Console.ReadLine();
    }
}

答案 1 :(得分:15)

JIT在某些情况下执行内联。我不知道它是否内联Math.Abs ...但您是否已经确认这实际上是一个性能问题?在您知道需要之前不要进行微观优化,然后通过以下方式衡量性能提升:

int d = X > 0 ? X : -X;

验证它真的值得。

正如安东尼所指出的,上述内容不会(通常)适用于int.MinValue-int.MinValue == int.MinValue,而Math.Abs会投放OverflowException。您也可以使用选中的算法在直接C#中强制执行此操作:

int d = X > 0 ? X : checked(-X);

答案 2 :(得分:6)

对于它的价值,32位带符号,2的补码格式int的绝对值通常是这样实现的:

abs(x)=(x ^(x>&gt; 31)) - (x>&gt; 31)

答案 3 :(得分:1)

我只看它是否小于零并乘以-1

int d = (X < 0) ? (-X) : X;

答案 4 :(得分:1)

有关如何在不进行分支的情况下计算绝对值,请参阅http://graphics.stanford.edu/~seander/bithacks.html#IntegerAbs

虽然.Net支持内联,但我怀疑Math.Abs()是否会被编译器视为内联的候选者。这是int重载的实现,由Reflector提供。

public static int Abs(int value)
{
  if (value >= 0)
    {
      return value;
    }
  return AbsHelper(value);
}

private static int AbsHelper(int value)
{
  if (value == -2147483648)
  {
    throw new OverflowException(Environment.GetResourceString("Overflow_NegateTwosCompNum"));
  }
  return -value;
}

其他整数类型的重载类似。 floatdouble重载是外部调用,而decimal重载使用自己的实现,构造新实例。哎哟!

答案 5 :(得分:1)

C#内联Math.Abs(),这是Math.Abs的C#和汇编代码(使用在线工具SharpLab生成):

C#:

public int test(int n){
    return Math.Abs(n);
}

组装:

L0000: push ebp
L0001: mov ebp, esp
L0003: test edx, edx
L0005: jge L000d
L0007: neg edx
L0009: test edx, edx
L000b: jl L0011
L000d: mov eax, edx
L000f: pop ebp
L0010: ret
L0011: call System.Math.ThrowAbsOverflow()
L0016: int3

答案 6 :(得分:0)

C#内联Math.Abs​​。这有效:

int x = 12;
int y = 17;
int z = Math.Abs(x - y);
Console.WriteLine(z); //outputs 5