Created
December 3, 2019 10:40
-
-
Save Sergio0694/1f48c898d95a1de1bbee69fba1d5c022 to your computer and use it in GitHub Desktop.
A benchmark of various no-branch methods to compute the max of two int values
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Buffers; | |
using System.Diagnostics.Contracts; | |
using System.Runtime.CompilerServices; | |
using BenchmarkDotNet.Attributes; | |
using BenchmarkDotNet.Running; | |
namespace MaxBenchmark | |
{ | |
class Program | |
{ | |
static void Main() | |
{ | |
BenchmarkRunner.Run<MaxBenchmark>(); | |
} | |
} | |
public class MaxBenchmark | |
{ | |
[Params(256, 10_000, 1_000_000)] | |
public int N; | |
public int[] A; | |
public int[] B; | |
public int[] C; | |
[GlobalSetup] | |
public void Setup() | |
{ | |
A = ArrayPool<int>.Shared.Rent(N); | |
B = ArrayPool<int>.Shared.Rent(N); | |
C = ArrayPool<int>.Shared.Rent(N); | |
var random = new Random(42); | |
for (int i = 0; i < N; i++) | |
{ | |
A[i] = random.Next(-N, N + 1); | |
B[i] = random.Next(-N, N + 1); | |
} | |
} | |
[GlobalCleanup] | |
public void Cleanup() | |
{ | |
ArrayPool<int>.Shared.Return(A); | |
ArrayPool<int>.Shared.Return(B); | |
ArrayPool<int>.Shared.Return(C); | |
} | |
[Benchmark] | |
public void Branch() | |
{ | |
int n = N; | |
ref int | |
ra = ref A[0], | |
rb = ref B[0], | |
rc = ref C[0]; | |
for (int i = 0; i < n; i++) | |
Unsafe.Add(ref rc, i) = MathI.Max0(Unsafe.Add(ref ra, i), Unsafe.Add(ref rb, i)); | |
} | |
[Benchmark] | |
public void NoBranch() | |
{ | |
int n = N; | |
ref int | |
ra = ref A[0], | |
rb = ref B[0], | |
rc = ref C[0]; | |
for (int i = 0; i < n; i++) | |
Unsafe.Add(ref rc, i) = MathI.Max1(Unsafe.Add(ref ra, i), Unsafe.Add(ref rb, i)); | |
} | |
[Benchmark] | |
public void NoBranch64bit() | |
{ | |
int n = N; | |
ref int | |
ra = ref A[0], | |
rb = ref B[0], | |
rc = ref C[0]; | |
for (int i = 0; i < n; i++) | |
Unsafe.Add(ref rc, i) = MathI.Max2(Unsafe.Add(ref ra, i), Unsafe.Add(ref rb, i)); | |
} | |
[Benchmark] | |
public void NoBranch32bitNoOverflow() | |
{ | |
int n = N; | |
ref int | |
ra = ref A[0], | |
rb = ref B[0], | |
rc = ref C[0]; | |
for (int i = 0; i < n; i++) | |
Unsafe.Add(ref rc, i) = MathI.Max3(Unsafe.Add(ref ra, i), Unsafe.Add(ref rb, i)); | |
} | |
} | |
public static class MathI | |
{ | |
/// <summary> | |
/// Computes the maximum value between two values with no branch instructions | |
/// </summary> | |
/// <param name="a">The first input value</param> | |
/// <param name="b">The second input value</param> | |
/// <returns>The maximum value between <paramref name="a"/> and <paramref name="b"/></returns> | |
[Pure] | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static int Max0(int a, int b) => a > b ? a : b; | |
/// <summary> | |
/// Computes the maximum value between two values with no branch instructions | |
/// </summary> | |
/// <param name="a">The first input value</param> | |
/// <param name="b">The second input value</param> | |
/// <returns>The maximum value between <paramref name="a"/> and <paramref name="b"/></returns> | |
[Pure] | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static int Max1(int a, int b) | |
{ | |
int sign = ((b - a) >> 31) & 1; | |
return a * sign + b * (sign ^ 1); | |
} | |
/// <summary> | |
/// Computes the maximum value between two values with no branch instructions | |
/// </summary> | |
/// <param name="a">The first input value</param> | |
/// <param name="b">The second input value</param> | |
/// <returns>The maximum value between <paramref name="a"/> and <paramref name="b"/></returns> | |
[Pure] | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static int Max2(int a, int b) | |
{ | |
long la = a, lb = b; | |
int sign = (int)(((lb - la) >> 63) & 1); | |
return a * sign + b * (sign ^ 1); | |
} | |
/// <summary> | |
/// Computes the maximum value between two values with no branch instructions | |
/// </summary> | |
/// <param name="a">The first input value</param> | |
/// <param name="b">The second input value</param> | |
/// <returns>The maximum value between <paramref name="a"/> and <paramref name="b"/></returns> | |
[Pure] | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static int Max3(int a, int b) | |
{ | |
int | |
c = a - b, | |
sa = 1 ^ ((a >> 31) & 1), | |
sb = 1 ^ ((b >> 31) & 1), | |
sc = 1 ^ ((c >> 31) & 1), | |
use_sa = sa ^ sb, | |
use_sc = 1 ^ (sa ^ sb), | |
k = use_sa * sa + use_sc * sc, | |
q = 1 ^ k; | |
return a * k + b * q; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment