Skip to content

Instantly share code, notes, and snippets.

@IEVin
Created January 14, 2020 12:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save IEVin/3045cfa67aef200d8c207cfb68bdf72c to your computer and use it in GitHub Desktop.
Save IEVin/3045cfa67aef200d8c207cfb68bdf72c to your computer and use it in GitHub Desktop.
Perfomance check to invoke operator+ from C# generic
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp2.2;netcoreapp3.1;net461;net472</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.0" />
</ItemGroup>
</Project>
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
namespace GenericTest
{
[SimpleJob(RuntimeMoniker.NetCoreApp31, baseline: true)]
[SimpleJob(RuntimeMoniker.CoreRt31)]
[SimpleJob(RuntimeMoniker.NetCoreApp22)]
[SimpleJob(RuntimeMoniker.CoreRt22)]
[SimpleJob(RuntimeMoniker.Net472)]
[SimpleJob(RuntimeMoniker.Net461)]
public class Test
{
[Params(1000,100000)]
public int N;
[Benchmark]
public int Sum_Operator()
{
int s = 0;
int i = N;
while(--i > 0)
s += i;
return s;
}
[Benchmark]
public int Sum_Unsafe()
{
int s = 0;
int i = N;
while(--i > 0)
s = NumHelperUnsafe.Add(s, i);
return s;
}
[Benchmark]
public int Sum_Object()
{
int s = 0;
int i = N;
while(--i > 0)
s = NumHelperObject.Add(s, i);
return s;
}
[Benchmark]
public int Sum_Cast()
{
int s = 0;
int i = N;
while(--i > 0)
s = NumHelperCast.Add(s, i);
return s;
}
[Benchmark]
public int Sum_Expression()
{
int s = 0;
int i = N;
while(--i > 0)
s = NumHelperExpression.Add(s, i);
return s;
}
[Benchmark]
public int Sum_Expression_NoDict()
{
int s = 0;
int i = N;
while(--i > 0)
s = NumHelperExpressionNoDict.Add(s, i);
return s;
}
}
static class NumHelperCast
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Add<T>(T left, T right)
where T : unmanaged
{
if(left is int i32Left && right is int i32Right)
{
var sum = i32Left + i32Right;
if(sum is T res)
return res;
}
throw new NotSupportedException();
}
}
static class NumHelperUnsafe
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Add<T>(T left, T right)
where T : unmanaged
{
if (typeof(T) == typeof(int))
{
int sum = Unsafe.As<T, int>(ref left) + Unsafe.As<T, int>(ref right);
return Unsafe.As<int, T>(ref sum);
}
throw new NotSupportedException();
}
}
static class NumHelperObject
{
[MethodImpl(MethodImplOptions.NoInlining)]
private static T ThrowInvalidOperation<T>() => throw new InvalidOperationException("Not support type");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Add<T>(T left, T right)
where T : unmanaged
{
if (typeof(T) == typeof(int))
{
return (T)(object)((int)(object)left + (int)(object)right);
}
return ThrowInvalidOperation<T>();
}
}
static class NumHelperExpression
{
static readonly Dictionary<(Type Type, string Op), Delegate> Cache =
new Dictionary<(Type Type, string Op), Delegate>();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Add<T>(T left, T right)
where T : unmanaged
{
var t = typeof(T);
// If op is cached by type and function name, use cached version
if (Cache.TryGetValue((t, nameof(Add)), out var del))
return del is Func<T, T, T> specificFunc
? specificFunc(left, right)
: throw new InvalidOperationException(nameof(Add));
var leftPar = Expression.Parameter(t, nameof(left));
var rightPar = Expression.Parameter(t, nameof(right));
var body = Expression.Add(leftPar, rightPar);
var func = Expression.Lambda<Func<T, T, T>>(body, leftPar, rightPar).Compile();
Cache[(t, nameof(Add))] = func;
return func(left, right);
}
}
static class NumHelperExpressionNoDict
{
static class Cache<T>
{
public static Func<T, T, T> AddFunc;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Add<T>(T left, T right)
where T : unmanaged
{
if (Cache<T>.AddFunc == null)
Cache<T>.AddFunc = Build_Add_Delegate<T>();
return Cache<T>.AddFunc(left, right);
}
[MethodImpl(MethodImplOptions.NoInlining)]
static Func<T,T,T> Build_Add_Delegate<T>()
{
var leftPar = Expression.Parameter(typeof(T), "left");
var rightPar = Expression.Parameter(typeof(T), "right");
var body = Expression.Add(leftPar, rightPar);
return Expression.Lambda<Func<T, T, T>>(body, leftPar, rightPar).Compile();
}
}
public class Program
{
public static void Main() => BenchmarkDotNet.Running.BenchmarkRunner.Run<Test>();
}
}
> dotnet run -c Release -f netcoreapp3.1
// * Summary *
BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.100
[Host] : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT
Job-TIFUJX : .NET Framework 4.8 (4.8.4042.0), X64 RyuJIT
Job-SCHUGI : .NET Framework 4.8 (4.8.4042.0), X64 RyuJIT
Job-NYZWUO : .NET Core 2.2.0 (CoreCLR 4.6.27110.04, CoreFX 4.6.27110.04), X64 RyuJIT
Job-HWNFWQ : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT
Job-JODAGG : .NET CoreRT 1.0.28514.01 @BuiltBy: dlab14-DDVSOWINAGE101 @Branch: master @Commit: 42f1bc077327d6e3647861bec00cfe0458187071, X64 AOT
Job-OJKGRE : .NET CoreRT 1.0.28514.01 @BuiltBy: dlab14-DDVSOWINAGE101 @Branch: master @Commit: 42f1bc077327d6e3647861bec00cfe0458187071, X64 AOT
| Method | Runtime | N | Mean | Error | StdDev | Median | Ratio | RatioSD |
|---------------------- |-------------- |------- |----------------:|--------------:|--------------:|----------------:|------:|--------:|
| Sum_Operator | .NET 4.6.1 | 1000 | 257.7 ns | 3.83 ns | 3.58 ns | 255.6 ns | 1.01 | 0.01 |
| Sum_Operator | .NET 4.7.2 | 1000 | 254.3 ns | 0.88 ns | 0.78 ns | 254.2 ns | 1.00 | 0.01 |
| Sum_Operator | .NET Core 2.2 | 1000 | 254.2 ns | 0.53 ns | 0.44 ns | 254.1 ns | 1.00 | 0.01 |
| Sum_Operator | .NET Core 3.1 | 1000 | 254.5 ns | 1.35 ns | 1.20 ns | 253.9 ns | 1.00 | 0.00 |
| Sum_Operator | CoreRt 2.2 | 1000 | 254.1 ns | 1.60 ns | 1.34 ns | 253.5 ns | 1.00 | 0.01 |
| Sum_Operator | CoreRt 3.1 | 1000 | 253.8 ns | 0.55 ns | 0.46 ns | 253.7 ns | 1.00 | 0.00 |
| | | | | | | | | |
| Sum_Unsafe | .NET 4.6.1 | 1000 | 254.5 ns | 0.77 ns | 0.68 ns | 254.2 ns | 0.99 | 0.01 |
| Sum_Unsafe | .NET 4.7.2 | 1000 | 256.9 ns | 3.98 ns | 3.72 ns | 254.9 ns | 1.00 | 0.02 |
| Sum_Unsafe | .NET Core 2.2 | 1000 | 257.3 ns | 3.72 ns | 2.91 ns | 258.0 ns | 1.00 | 0.02 |
| Sum_Unsafe | .NET Core 3.1 | 1000 | 256.6 ns | 3.27 ns | 3.06 ns | 255.9 ns | 1.00 | 0.00 |
| Sum_Unsafe | CoreRt 2.2 | 1000 | 255.0 ns | 2.01 ns | 1.88 ns | 254.7 ns | 0.99 | 0.01 |
| Sum_Unsafe | CoreRt 3.1 | 1000 | 255.8 ns | 3.36 ns | 3.14 ns | 254.0 ns | 1.00 | 0.02 |
| | | | | | | | | |
| Sum_Object | .NET 4.6.1 | 1000 | 255.7 ns | 3.38 ns | 3.00 ns | 254.3 ns | 1.00 | 0.02 |
| Sum_Object | .NET 4.7.2 | 1000 | 258.4 ns | 3.52 ns | 2.94 ns | 259.4 ns | 1.01 | 0.02 |
| Sum_Object | .NET Core 2.2 | 1000 | 254.3 ns | 0.50 ns | 0.44 ns | 254.3 ns | 0.99 | 0.01 |
| Sum_Object | .NET Core 3.1 | 1000 | 256.5 ns | 2.86 ns | 2.67 ns | 255.4 ns | 1.00 | 0.00 |
| Sum_Object | CoreRt 2.2 | 1000 | 256.5 ns | 4.56 ns | 4.04 ns | 255.2 ns | 1.00 | 0.01 |
| Sum_Object | CoreRt 3.1 | 1000 | 257.3 ns | 4.40 ns | 3.90 ns | 256.5 ns | 1.00 | 0.01 |
| | | | | | | | | |
| Sum_Cast | .NET 4.6.1 | 1000 | 16,972.8 ns | 269.92 ns | 252.48 ns | 17,076.4 ns | 1.97 | 0.04 |
| Sum_Cast | .NET 4.7.2 | 1000 | 16,779.3 ns | 52.81 ns | 49.40 ns | 16,774.1 ns | 1.94 | 0.03 |
| Sum_Cast | .NET Core 2.2 | 1000 | 9,047.8 ns | 27.16 ns | 22.68 ns | 9,050.5 ns | 1.05 | 0.01 |
| Sum_Cast | .NET Core 3.1 | 1000 | 8,633.2 ns | 128.14 ns | 119.86 ns | 8,596.5 ns | 1.00 | 0.00 |
| Sum_Cast | CoreRt 2.2 | 1000 | 7,958.4 ns | 129.17 ns | 120.83 ns | 7,917.4 ns | 0.92 | 0.02 |
| Sum_Cast | CoreRt 3.1 | 1000 | 7,914.6 ns | 155.58 ns | 152.80 ns | 7,874.4 ns | 0.92 | 0.03 |
| | | | | | | | | |
| Sum_Expression | .NET 4.6.1 | 1000 | 65,420.5 ns | 246.68 ns | 205.99 ns | 65,337.5 ns | 1.16 | 0.01 |
| Sum_Expression | .NET 4.7.2 | 1000 | 82,839.0 ns | 226.74 ns | 201.00 ns | 82,825.2 ns | 1.47 | 0.01 |
| Sum_Expression | .NET Core 2.2 | 1000 | 66,329.4 ns | 719.60 ns | 637.91 ns | 66,071.0 ns | 1.18 | 0.01 |
| Sum_Expression | .NET Core 3.1 | 1000 | 56,396.9 ns | 565.17 ns | 528.66 ns | 56,163.1 ns | 1.00 | 0.00 |
| Sum_Expression | CoreRt 2.2 | 1000 | 220,401.6 ns | 574.99 ns | 480.14 ns | 220,320.9 ns | 3.90 | 0.04 |
| Sum_Expression | CoreRt 3.1 | 1000 | 215,887.3 ns | 475.28 ns | 421.32 ns | 215,961.0 ns | 3.83 | 0.04 |
| | | | | | | | | |
| Sum_Expression_NoDict | .NET 4.6.1 | 1000 | 2,462.0 ns | 8.31 ns | 7.37 ns | 2,459.3 ns | 0.95 | 0.00 |
| Sum_Expression_NoDict | .NET 4.7.2 | 1000 | 2,223.2 ns | 15.10 ns | 13.38 ns | 2,218.8 ns | 0.85 | 0.01 |
| Sum_Expression_NoDict | .NET Core 2.2 | 1000 | 2,342.4 ns | 7.57 ns | 7.08 ns | 2,342.7 ns | 0.90 | 0.00 |
| Sum_Expression_NoDict | .NET Core 3.1 | 1000 | 2,602.0 ns | 12.92 ns | 10.79 ns | 2,603.3 ns | 1.00 | 0.00 |
| Sum_Expression_NoDict | CoreRt 2.2 | 1000 | 128,665.7 ns | 2,830.54 ns | 8,256.82 ns | 123,459.1 ns | 49.17 | 3.48 |
| Sum_Expression_NoDict | CoreRt 3.1 | 1000 | 125,284.1 ns | 5,474.24 ns | 9,587.69 ns | 120,077.6 ns | 49.86 | 4.23 |
| | | | | | | | | |
| Sum_Operator | .NET 4.6.1 | 100000 | 24,702.0 ns | 54.07 ns | 45.15 ns | 24,681.9 ns | 1.00 | 0.00 |
| Sum_Operator | .NET 4.7.2 | 100000 | 24,722.9 ns | 74.59 ns | 62.29 ns | 24,707.5 ns | 1.00 | 0.00 |
| Sum_Operator | .NET Core 2.2 | 100000 | 24,760.5 ns | 103.70 ns | 97.00 ns | 24,738.8 ns | 1.00 | 0.00 |
| Sum_Operator | .NET Core 3.1 | 100000 | 24,687.8 ns | 45.52 ns | 35.54 ns | 24,702.1 ns | 1.00 | 0.00 |
| Sum_Operator | CoreRt 2.2 | 100000 | 24,777.1 ns | 192.64 ns | 170.77 ns | 24,706.2 ns | 1.00 | 0.01 |
| Sum_Operator | CoreRt 3.1 | 100000 | 24,833.9 ns | 258.02 ns | 228.73 ns | 24,704.7 ns | 1.01 | 0.01 |
| | | | | | | | | |
| Sum_Unsafe | .NET 4.6.1 | 100000 | 24,841.8 ns | 369.33 ns | 327.41 ns | 24,692.1 ns | 1.01 | 0.01 |
| Sum_Unsafe | .NET 4.7.2 | 100000 | 24,767.4 ns | 96.90 ns | 75.65 ns | 24,743.4 ns | 1.00 | 0.00 |
| Sum_Unsafe | .NET Core 2.2 | 100000 | 24,749.5 ns | 52.50 ns | 49.11 ns | 24,750.6 ns | 1.00 | 0.00 |
| Sum_Unsafe | .NET Core 3.1 | 100000 | 24,686.5 ns | 78.30 ns | 65.39 ns | 24,665.0 ns | 1.00 | 0.00 |
| Sum_Unsafe | CoreRt 2.2 | 100000 | 24,688.1 ns | 32.07 ns | 29.99 ns | 24,697.1 ns | 1.00 | 0.00 |
| Sum_Unsafe | CoreRt 3.1 | 100000 | 24,668.0 ns | 20.58 ns | 17.18 ns | 24,666.2 ns | 1.00 | 0.00 |
| | | | | | | | | |
| Sum_Object | .NET 4.6.1 | 100000 | 24,787.4 ns | 102.09 ns | 90.50 ns | 24,795.0 ns | 1.00 | 0.01 |
| Sum_Object | .NET 4.7.2 | 100000 | 24,901.4 ns | 263.32 ns | 233.43 ns | 24,803.0 ns | 1.00 | 0.01 |
| Sum_Object | .NET Core 2.2 | 100000 | 24,741.5 ns | 79.86 ns | 70.79 ns | 24,750.7 ns | 1.00 | 0.01 |
| Sum_Object | .NET Core 3.1 | 100000 | 24,785.7 ns | 220.07 ns | 195.09 ns | 24,694.5 ns | 1.00 | 0.00 |
| Sum_Object | CoreRt 2.2 | 100000 | 25,286.9 ns | 367.15 ns | 343.44 ns | 25,394.8 ns | 1.02 | 0.01 |
| Sum_Object | CoreRt 3.1 | 100000 | 24,690.4 ns | 35.01 ns | 31.04 ns | 24,694.0 ns | 1.00 | 0.01 |
| | | | | | | | | |
| Sum_Cast | .NET 4.6.1 | 100000 | 1,652,707.2 ns | 6,028.82 ns | 5,034.33 ns | 1,654,539.1 ns | 1.52 | 0.02 |
| Sum_Cast | .NET 4.7.2 | 100000 | 1,664,312.5 ns | 7,826.80 ns | 7,321.19 ns | 1,661,624.2 ns | 1.54 | 0.02 |
| Sum_Cast | .NET Core 2.2 | 100000 | 907,366.1 ns | 1,957.60 ns | 1,735.36 ns | 907,544.9 ns | 0.84 | 0.01 |
| Sum_Cast | .NET Core 3.1 | 100000 | 1,081,171.7 ns | 17,976.53 ns | 16,815.26 ns | 1,074,136.1 ns | 1.00 | 0.00 |
| Sum_Cast | CoreRt 2.2 | 100000 | 792,101.2 ns | 14,186.87 ns | 12,576.29 ns | 787,770.9 ns | 0.73 | 0.02 |
| Sum_Cast | CoreRt 3.1 | 100000 | 799,300.3 ns | 13,187.41 ns | 11,690.29 ns | 798,088.5 ns | 0.74 | 0.01 |
| | | | | | | | | |
| Sum_Expression | .NET 4.6.1 | 100000 | 6,575,780.3 ns | 23,228.01 ns | 18,134.90 ns | 6,583,162.1 ns | 1.12 | 0.00 |
| Sum_Expression | .NET 4.7.2 | 100000 | 8,308,984.2 ns | 30,022.00 ns | 28,082.60 ns | 8,299,875.0 ns | 1.42 | 0.00 |
| Sum_Expression | .NET Core 2.2 | 100000 | 6,640,131.8 ns | 30,789.51 ns | 25,710.63 ns | 6,637,887.5 ns | 1.13 | 0.00 |
| Sum_Expression | .NET Core 3.1 | 100000 | 5,869,053.8 ns | 8,064.09 ns | 7,543.16 ns | 5,865,239.8 ns | 1.00 | 0.00 |
| Sum_Expression | CoreRt 2.2 | 100000 | 21,457,534.8 ns | 50,772.21 ns | 45,008.24 ns | 21,468,464.1 ns | 3.66 | 0.01 |
| Sum_Expression | CoreRt 3.1 | 100000 | 21,525,873.5 ns | 195,601.94 ns | 182,966.18 ns | 21,441,962.5 ns | 3.67 | 0.03 |
| | | | | | | | | |
| Sum_Expression_NoDict | .NET 4.6.1 | 100000 | 238,786.1 ns | 664.45 ns | 589.02 ns | 238,900.3 ns | 0.88 | 0.02 |
| Sum_Expression_NoDict | .NET 4.7.2 | 100000 | 222,201.8 ns | 551.43 ns | 515.80 ns | 222,291.8 ns | 0.82 | 0.01 |
| Sum_Expression_NoDict | .NET Core 2.2 | 100000 | 235,319.4 ns | 419.77 ns | 327.73 ns | 235,285.7 ns | 0.87 | 0.02 |
| Sum_Expression_NoDict | .NET Core 3.1 | 100000 | 270,398.5 ns | 5,057.83 ns | 4,731.10 ns | 269,102.2 ns | 1.00 | 0.00 |
| Sum_Expression_NoDict | CoreRt 2.2 | 100000 | 12,274,687.0 ns | 92,672.68 ns | 72,352.73 ns | 12,259,372.7 ns | 45.48 | 0.90 |
| Sum_Expression_NoDict | CoreRt 3.1 | 100000 | 11,974,665.6 ns | 61,162.26 ns | 47,751.47 ns | 11,975,140.6 ns | 44.37 | 0.91 |
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment