Skip to content

Instantly share code, notes, and snippets.

@ufcpp
Last active May 18, 2018 05:08
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Embed
What would you like to do?
ジェネリックな typeof(T) == typeof(...) は JIT 時最適化で消えるかどうか
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);
}
}
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