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)
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:
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
AbsHelperabove) 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.Absto something likeThis 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?
In my own testing this has a slight perf hit over the merged implementation of
Math.Abswhen 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.