runtime: Math.Abs is slow

Math.Abs is slow when the value is negative


BenchmarkDotNet=v0.10.10, OS=Windows 10 Redstone 3 [1709, Fall Creators Update] (10.0.16299.192)
Processor=Intel Core i7-7700K CPU 4.20GHz (Kaby Lake), ProcessorCount=8
.NET Core SDK=2.1.2
  [Host]     : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT  [AttachedDebugger]
  DefaultJob : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT


Method x Mean Error StdDev
If -100 0.2354 ns 0.0050 ns 0.0044 ns
MyAbs -100 0.2350 ns 0.0037 ns 0.0034 ns
MathAbs -100 1.0940 ns 0.0019 ns 0.0016 ns
If 100 0.2307 ns 0.0013 ns 0.0011 ns
MyAbs 100 0.2297 ns 0.0011 ns 0.0010 ns
MathAbs 100 0.1939 ns 0.0009 ns 0.0008 ns
public class AbsBench
{
    [Params(100,-100)]
    public int x;
    public int y;

    [Benchmark]
    public void If()
    {
        y = x < 0 ? (x == int.MinValue ? throw new OverflowException() : -x) : x;
    }

    [Benchmark]
    public void MyAbs()
    {
        y = AbsHelper.Abs(x);
    }


    [Benchmark]
    public void MathAbs()
    {
        y = Math.Abs(x);
    }
}

public static class AbsHelper
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int Abs(int x)
        => x < 0 ? (x == int.MinValue ? throw new OverflowException() : -x) : x;

}

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 17 (17 by maintainers)

Most upvoted comments

The general guidance is to separate out throws into helper methods that do the work of creating the exception object and any related data (eg formatted exception messages) and then unconditionally throw. The jit’s inline performance heuristic will then block inlining of the helper. This has a number of performance benefits:

  • overall code size savings when are multiple callers or callers with multiple throw sites
  • call sites to helper are considered “rare” and so moved into the caller’s cold code region
  • helper IL is only jitted if an exception about to be thrown, so caller jits faster
  • caller’s prolog/epilog may be simplified with fewer register saves/restores

Native codegen for exception throws that use resource based strings is surprisingly large.

There is no “correctness” reason preventing methods with throws from being inlined, and methods that conditionally throw (like the original AbsHelper above) may end up getting inlined, as they might contain a mixture of hot and cold code. Methods that unconditionally throw are much less likely to contain any hot code.

We should probably change Math.Abs to something like

static int Abs(int value)
{
    if (value < 0)
    {
        value = -value;
        if (value < 0)
        {
            AbsHelper();
        }
    }
    return value;
}

This produces the smallest amount of code with the current JIT and there’s some room for improvement as well.

The current implementation is curious. Someone automagically decided that negative numbers are uncommon and thus only the positive case should be handled efficiently 😕

@mikedn @benaadams It was mentioned earlier that this code path is only optimized for the non-negative input case. Would branchless code like the below address this?

public static int Abs(int value) {
    int sign = (value >> 31); // -1 if negative; 0 if non-negative
    value ^= sign; // ~value if negative; value if non-negative
    value -= sign; // ~value + 1 (= -value) if negative; value if non-negative
    if (value < 0) { /* throw - should pretty much always evaluate to false in branch predictor */ }
    return value;
}

In my own testing this has a slight perf hit over the merged implementation of Math.Abs when the branch predictor can perfectly guess whether the input value is negative. It has a significant perf gain over the merged implementation for random input data.