ジェネリックな typeof(T) == typeof(...) は JIT 時最適化で消えるかどうか
This file contains 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 BenchmarkDotNet.Attributes; | |
| using BenchmarkDotNet.Running; | |
| using BenchmarkDotNet.Configs; | |
| using BenchmarkDotNet.Jobs; | |
| using BenchmarkDotNet.Toolchains.CsProj; | |
| using BenchmarkDotNet.Columns; | |
| using BenchmarkDotNet.Diagnosers; | |
| using BenchmarkDotNet.Exporters; | |
| using BenchmarkDotNet.Loggers; | |
| using System.Runtime.CompilerServices; | |
| interface IX { int M(); } | |
| class C: IX { public int M() => 1; } | |
| struct S : IX { public int M() => 2; } | |
| /// <summary> | |
| /// | |
| /// ジェネリック型引数 T に対して typeof(T) == typeof(int) みたいなのとか、T x に対して x is Interface みたいなのが最適化で消えるかどうか。 | |
| /// | |
| /// 計測結果: | |
| /// Method | Toolchain | Mean | Error | StdDev | Gen 0 | Allocated | | |
| /// ------------------------ |-------------- |------------:|-----------:|-----------:|--------:|----------:| | |
| /// CallGenericClass | .NET Core 1.1 | 4,240.2 ns | 123.224 ns | 121.023 ns | - | 24 B | | |
| /// CallNonGenericClass | .NET Core 1.1 | 268.5 ns | 1.597 ns | 1.494 ns | 0.0057 | 24 B | | |
| /// CallGenericStruct | .NET Core 1.1 | 11,214.6 ns | 134.929 ns | 126.212 ns | 11.4288 | 48000 B | | |
| /// CallNonGenericStruct | .NET Core 1.1 | 261.3 ns | 1.841 ns | 1.722 ns | - | 0 B | | |
| /// CallGenericPrimitive | .NET Core 1.1 | 2,796.5 ns | 8.728 ns | 8.165 ns | - | 0 B | | |
| /// CallNonGenericPrimitive | .NET Core 1.1 | 2,795.1 ns | 10.441 ns | 9.766 ns | - | 0 B | | |
| /// CallGenericClass | .NET Core 2.0 | 4,104.9 ns | 56.357 ns | 49.959 ns | - | 24 B | | |
| /// CallNonGenericClass | .NET Core 2.0 | 530.2 ns | 3.270 ns | 2.730 ns | 0.0048 | 24 B | | |
| /// CallGenericStruct | .NET Core 2.0 | 10,064.8 ns | 169.126 ns | 194.766 ns | 11.4288 | 48000 B | | |
| /// CallNonGenericStruct | .NET Core 2.0 | 268.2 ns | 3.217 ns | 2.852 ns | - | 0 B | | |
| /// CallGenericPrimitive | .NET Core 2.0 | 2,798.0 ns | 27.580 ns | 25.798 ns | - | 0 B | | |
| /// CallNonGenericPrimitive | .NET Core 2.0 | 2,809.4 ns | 23.743 ns | 22.209 ns | - | 0 B | | |
| /// CallGenericClass | .NET Core 2.1 | 263.8 ns | 2.416 ns | 2.142 ns | - | 0 B | | |
| /// CallNonGenericClass | .NET Core 2.1 | 265.6 ns | 4.504 ns | 3.761 ns | - | 0 B | | |
| /// CallGenericStruct | .NET Core 2.1 | 325.5 ns | 4.299 ns | 3.811 ns | - | 0 B | | |
| /// CallNonGenericStruct | .NET Core 2.1 | 262.9 ns | 3.479 ns | 3.254 ns | - | 0 B | | |
| /// CallGenericPrimitive | .NET Core 2.1 | 2,783.8 ns | 16.269 ns | 15.218 ns | - | 0 B | | |
| /// CallNonGenericPrimitive | .NET Core 2.1 | 2,799.3 ns | 28.134 ns | 26.317 ns | - | 0 B | | |
| /// | |
| /// .NET Core 1.1 でも、typeof(T) == typeof(int) に対する最適化はかかってるみたい。 | |
| /// x is Interface に対する最適化は .NET Core 2.1 から。 | |
| /// | |
| /// Generic って付いてるのと NonGeneric って付いてるので Mean の値が同じものは最適化がちゃんと効いてる。 | |
| /// 対応さえしていれば、非ジェネリック・分岐なし版と結構そん色ないレベルに最適化されてるっぽい。 | |
| /// (struct の場合だけちょっとだけペナルティかかってるけど、桁が変わりはしない。) | |
| /// | |
| /// 対応していないやつ(.NET Core 2.0 までの x is Interface の方)では、1桁差があるんで、たぶん「インライン展開がかかるかどうかの有無」から差がありそう。 | |
| /// 構造体の場合は「ボックス化が起こるかどうか」の差まであってかなりの時間差になってる(というか allocation がヤバイ)。 | |
| /// </summary> | |
| [MemoryDiagnoser] | |
| public class B | |
| { | |
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |
| private static int Generic<T>(T x) | |
| { | |
| if (typeof(T) == typeof(bool)) return 100; | |
| else if (typeof(T) == typeof(byte)) return 101; | |
| else if (typeof(T) == typeof(sbyte)) return 102; | |
| else if (typeof(T) == typeof(ushort)) return 103; | |
| else if (typeof(T) == typeof(short)) return 104; | |
| else if (typeof(T) == typeof(int)) return 105; | |
| else if (typeof(T) == typeof(uint)) return 106; | |
| else if (typeof(T) == typeof(ulong)) return 107; | |
| else if (typeof(T) == typeof(long)) return 108; | |
| else if (typeof(T) == typeof(float)) return 109; | |
| else if (typeof(T) == typeof(double)) return 110; | |
| else if (typeof(T) == typeof(decimal)) return 111; | |
| else if (x is IX) return ((IX)x).M(); | |
| else return -1; | |
| } | |
| private int NonGeneric(bool x) => 100; | |
| private int NonGeneric(sbyte x) => 101; | |
| private int NonGeneric(byte x) => 102; | |
| private int NonGeneric(ushort x) => 103; | |
| private int NonGeneric(short x) => 104; | |
| private int NonGeneric(uint x) => 105; | |
| private int NonGeneric(int x) => 106; | |
| private int NonGeneric(ulong x) => 107; | |
| private int NonGeneric(long x) => 108; | |
| private int NonGeneric(float x) => 109; | |
| private int NonGeneric(double x) => 110; | |
| private int NonGeneric(decimal x) => 111; | |
| private int NonGeneric(C x) => x?.M() ?? -1; // クラスに対しては if (x is IX) return ((IX)x).M(); が null チェックを兼ねちゃうはず | |
| private int NonGeneric(S x) => x.M(); | |
| const int Loops = 1000; | |
| [Benchmark] | |
| public int CallGenericClass() | |
| { | |
| var sum = 0; | |
| var x = new C(); | |
| for (int i = 0; i < Loops; i++) sum += Generic(x); | |
| return sum; | |
| } | |
| [Benchmark] | |
| public int CallNonGenericClass() | |
| { | |
| var sum = 0; | |
| var x = new C(); | |
| for (int i = 0; i < Loops; i++) sum += NonGeneric(x); | |
| return sum; | |
| } | |
| [Benchmark] | |
| public int CallGenericStruct() | |
| { | |
| var sum = 0; | |
| var x = new S(); | |
| for (int i = 0; i < Loops; i++) sum += Generic(x); | |
| return sum; | |
| } | |
| [Benchmark] | |
| public int CallNonGenericStruct() | |
| { | |
| var sum = 0; | |
| var x = new S(); | |
| for (int i = 0; i < Loops; i++) sum += NonGeneric(x); | |
| return sum; | |
| } | |
| [Benchmark] | |
| public int CallGenericPrimitive() | |
| { | |
| var sum = 0; | |
| for (int i = 0; i < Loops; i++) | |
| { | |
| sum += Generic((byte)i); | |
| sum += Generic((sbyte)i); | |
| sum += Generic((ushort)i); | |
| sum += Generic((short)i); | |
| sum += Generic(i); | |
| sum += Generic((uint)i); | |
| sum += Generic((long)i); | |
| sum += Generic((ulong)i); | |
| sum += Generic(3.14f * i); | |
| sum += Generic(3.14 * i); | |
| sum += Generic((i & 1) == 0); | |
| } | |
| return sum; | |
| } | |
| [Benchmark] | |
| public int CallNonGenericPrimitive() | |
| { | |
| var sum = 0; | |
| for (int i = 0; i < Loops; i++) | |
| { | |
| sum += NonGeneric((byte)i); | |
| sum += NonGeneric((sbyte)i); | |
| sum += NonGeneric((ushort)i); | |
| sum += NonGeneric((short)i); | |
| sum += NonGeneric(i); | |
| sum += NonGeneric((uint)i); | |
| sum += NonGeneric((long)i); | |
| sum += NonGeneric((ulong)i); | |
| sum += NonGeneric(3.14f * i); | |
| sum += NonGeneric(3.14 * i); | |
| sum += NonGeneric((i & 1) == 0); | |
| } | |
| return sum; | |
| } | |
| } | |
| class Program | |
| { | |
| static void Main() | |
| { | |
| BenchmarkRunner.Run<B>(new MultipleRuntimesConfig()); | |
| } | |
| } | |
| public class MultipleRuntimesConfig : ManualConfig | |
| { | |
| public MultipleRuntimesConfig() | |
| { | |
| Add(Job.Default.With(CsProjCoreToolchain.NetCoreApp11)); | |
| Add(Job.Default.With(CsProjCoreToolchain.NetCoreApp20)); | |
| Add(Job.Default.With(CsProjCoreToolchain.NetCoreApp21)); | |
| Add(DefaultColumnProviders.Instance); | |
| Add(MarkdownExporter.GitHub); | |
| Add(new ConsoleLogger()); | |
| Add(new HtmlExporter()); | |
| Add(MemoryDiagnoser.Default); | |
| } | |
| } |
This file contains 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 BenchmarkDotNet.Attributes; | |
| using BenchmarkDotNet.Running; | |
| using BenchmarkDotNet.Configs; | |
| using BenchmarkDotNet.Jobs; | |
| using BenchmarkDotNet.Toolchains.CsProj; | |
| using BenchmarkDotNet.Columns; | |
| using BenchmarkDotNet.Diagnosers; | |
| using BenchmarkDotNet.Exporters; | |
| using BenchmarkDotNet.Loggers; | |
| using System; | |
| interface IStaticDelegate<T1, TResult> | |
| { | |
| TResult Invoke(T1 arg); | |
| } | |
| interface IStaticDelegate<T1, T2, TResult> | |
| { | |
| TResult Invoke(T1 arg1, T2 arg2); | |
| } | |
| struct Int | |
| { | |
| public int Value; | |
| public Int(int value) => Value = value; | |
| } | |
| static class IntArithmetic | |
| { | |
| public static readonly Func<int, Int, int> StaticAdd = _StaticAdd; | |
| static int _StaticAdd(int x, Int y) => x + y.Value; | |
| public static readonly Func<int, Int, int> CurriedAdd = default(object)._CurriedAdd; | |
| static int _CurriedAdd(this object _, int x, Int y) => x + y.Value; | |
| public struct ShapeAdd : IStaticDelegate<int, Int, int> | |
| { | |
| public int Invoke(int arg1, Int arg2) => arg1 + arg2.Value; | |
| } | |
| } | |
| /// <summary> | |
| /// </summary> | |
| [MemoryDiagnoser] | |
| public class B | |
| { | |
| const int N = 1000; | |
| Int[] _data; | |
| [GlobalSetup] | |
| public void Setup() | |
| { | |
| var data = new Int[N]; | |
| for (int i = 0; i < N; i++) data[i] = new Int(i); | |
| _data = data; | |
| } | |
| private int? Sum(Int[] data) | |
| { | |
| if (data == null) return null; | |
| var sum = 0; | |
| foreach (var x in data) sum += x.Value; | |
| return sum; | |
| } | |
| private int? Sum<T>(T[] data, Func<int, T, int> add) | |
| { | |
| if (data == null) return null; | |
| var sum = 0; | |
| foreach (var x in data) sum = add(sum, x); | |
| return sum; | |
| } | |
| private int? Sum<T, TSum>(T[] data) | |
| where TSum : struct, IStaticDelegate<int, T, int> | |
| { | |
| if (data == null) return null; | |
| var add = default(TSum); | |
| var sum = 0; | |
| foreach (var x in data) sum = add.Invoke(sum, x); | |
| return sum; | |
| } | |
| [Benchmark] | |
| public int? StaticAdd() => Sum(_data, IntArithmetic.StaticAdd); | |
| [Benchmark] | |
| public int? CurriedAdd() => Sum(_data, IntArithmetic.CurriedAdd); | |
| [Benchmark] | |
| public int? ShapeAdd() => Sum<Int, IntArithmetic.ShapeAdd>(_data); | |
| [Benchmark] | |
| public int? Add() => Sum(_data); | |
| } | |
| class Program | |
| { | |
| static void Main() | |
| { | |
| //var x = new B(); | |
| //x.Setup(); | |
| //Console.WriteLine((x.StaticAdd(), x.CurriedAdd(), x.ShapeAdd(), x.Add(), (1000 * 999) / 2)); | |
| BenchmarkRunner.Run<B>(new MultipleRuntimesConfig()); | |
| } | |
| } | |
| public class MultipleRuntimesConfig : ManualConfig | |
| { | |
| public MultipleRuntimesConfig() | |
| { | |
| //Add(Job.Default.With(CsProjCoreToolchain.NetCoreApp20)); | |
| Add(Job.Default.With(CsProjCoreToolchain.NetCoreApp21)); | |
| Add(DefaultColumnProviders.Instance); | |
| Add(MarkdownExporter.GitHub); | |
| Add(new ConsoleLogger()); | |
| Add(new HtmlExporter()); | |
| Add(MemoryDiagnoser.Default); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment