快速整数ABS功能

本文关键字:功能 ABS 整数 | 更新日期: 2023-09-27 17:57:38

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

我确信.NET不会进行内联。那么,我会做if((吗,还是有其他鲜为人知的技巧?

快速整数ABS功能

我做了一些性能测试,以了解使用标准Math.Abs.之外的东西是否真的可以节省时间

执行所有这些2000000000次后的结果(i从-10000000到+100000000,因此没有溢出(:

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相比,您可以得到非常轻微的改进,但没有什么了不起的。

使用bit hack,您可以节省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();
    }
}

JIT在某些情况下执行内联。我不知道它是否内联了Math.Abs。。。但是,您是否验证了这实际上是您的性能问题?在知道需要之前不要进行微观优化,然后测量之类的东西的性能增益

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

以验证它是否真的值得。

正如Anthony所指出的,上面的内容(通常(不适用于int.MinValue,如-int.MinValue == int.MinValue,而Math.Abs将抛出一个OverflowException。您也可以使用检查过的算法在C#中强制执行此操作:

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

就其价值而言,32位有符号的2的补码格式int的绝对值通常是这样实现的:

abs(x(=(x^(x>>31((-(x>>31(

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

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

请参阅http://graphics.stanford.edu/~seander/bithacks.html#IntegerAbs了解如何在不分支的情况下计算绝对值。

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

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重载使用自己的实现,该实现构造了一个新实例。哎哟

如果你想要真正的性能,试试这个:

    int result = source & 0x7FFFFFFF;

它使用位AND运算符('&'(并过滤掉符号位,其余部分保持原样。
0x7FFFFFFF是(1<<31(的十六进制值,适用于int32(默认int(

有关位操作的更多信息,请访问此处:
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/bitwise-and-shift-operators

我的这个和其他功能的时间如下(ryzen 3900x(:

    Math.Abs(i):        2224ms
    i>0?i:-1   :         958ms
    (i+(i>>31))^(i>>31): 953ms
    i&0x7FFFFFFF:        486ms

C#执行内联Math.Abs。这很有效:

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

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

如果你知道这是关于最小化问题的差异,你可以使用:a<bb-a:a-b

用数学方法计算x的abs很简单:

sqrt(x^2)