rksoftware

Visual Studio とか C# とかが好きです

メソッドの引数の型違いでパフォーマンス計測 (2)

メソッドの引数の違いでのパフォーマンス計測をして見始めました。
rksoftware.hatenablog.com
計測には BenchmarkDotNet ( NuGet Gallery | BenchmarkDotNet 0.13.11 ) を使用してみました。

今回は、メソッドの定義上の引数の型でなく、実際に引数に渡す変数の実際の型でどうなるか見てみます。

検証コード 1

namespace ClassLibrary1
{
    public static class MethodExtensions
    {
        public static int ExtensionMethod(this object s) => 0;
        public static int ExtensionMethodInt(this int s) => 0;
        public static int ExtensionMethodString(this string s) => 0;
    }
}
using ClassLibrary1;

BenchmarkDotNet.Running.BenchmarkRunner.Run<Sample>(new BenchmarkDotNet.Configs.DebugInProcessConfig());

public class Sample
{
    int[] ints = new int[short.MaxValue];
    object o = new object();
    object o_i = 1;
    object o_s = "";
    int i = 0;
    string s = "";
    [BenchmarkDotNet.Attributes.Benchmark]
    public void MethodObject() { foreach (var c in ints) o.ExtensionMethod(); }
    [BenchmarkDotNet.Attributes.Benchmark]
    public void MethodObjectInt() { foreach (var c in ints) o_i.ExtensionMethod(); }
    [BenchmarkDotNet.Attributes.Benchmark]
    public void MethodIntObject() { foreach (var c in ints) ((object)i).ExtensionMethod(); }
    [BenchmarkDotNet.Attributes.Benchmark]
    public void MethodObjectString() { foreach (var c in ints) o_s.ExtensionMethod(); }
    [BenchmarkDotNet.Attributes.Benchmark]
    public void MethodStringObject() { foreach (var c in ints) ((object)s).ExtensionMethod(); }
    [BenchmarkDotNet.Attributes.Benchmark]
    public void MethodInt() { foreach (var c in ints) i.ExtensionMethodInt(); }
    [BenchmarkDotNet.Attributes.Benchmark]
    public void MethodString() { foreach (var c in ints) s.ExtensionMethodString(); }
}

実行

1 回目

Method Mean Error StdDev
MethodObject 12.46 us 0.274 us 0.737 us
MethodObjectInt 11.40 us 0.225 us 0.490 us
MethodIntObject 11.16 us 0.206 us 0.183 us
MethodObjectString 11.10 us 0.216 us 0.202 us
MethodStringObject 11.00 us 0.177 us 0.157 us
MethodInt 11.29 us 0.218 us 0.469 us
MethodString 11.14 us 0.223 us 0.305 us

2 回目

Method Mean Error StdDev Median
MethodObject 11.98 us 0.239 us 0.276 us 11.98 us
MethodObjectInt 11.34 us 0.226 us 0.501 us 11.16 us
MethodIntObject 11.14 us 0.210 us 0.295 us 11.08 us
MethodObjectString 10.90 us 0.213 us 0.178 us 10.86 us
MethodStringObject 10.95 us 0.216 us 0.337 us 10.90 us
MethodInt 10.94 us 0.207 us 0.358 us 10.83 us
MethodString 13.21 us 0.728 us 2.112 us 13.03 us

3 回目

Method Mean Error StdDev Median
MethodObject 11.79 us 0.225 us 0.292 us 11.71 us
MethodObjectInt 11.09 us 0.213 us 0.167 us 11.09 us
MethodIntObject 11.25 us 0.222 us 0.515 us 11.05 us
MethodObjectString 11.15 us 0.223 us 0.575 us 10.90 us
MethodStringObject 10.85 us 0.143 us 0.140 us 10.85 us
MethodInt 11.02 us 0.219 us 0.300 us 10.96 us
MethodString 11.06 us 0.216 us 0.337 us 10.94 us

4 回目

Method Mean Error StdDev
MethodObject 11.90 us 0.232 us 0.348 us
MethodObjectInt 11.43 us 0.226 us 0.481 us
MethodIntObject 11.03 us 0.161 us 0.143 us
MethodObjectString 11.19 us 0.220 us 0.379 us
MethodStringObject 11.08 us 0.215 us 0.211 us
MethodInt 11.16 us 0.218 us 0.327 us
MethodString 11.06 us 0.216 us 0.231 us

■ 結果

ほとんど変わりませんね。けれど、object 型のオブジェクトの場合が遅いということは言えそうです。
あと気持ち程度、int や string のメソッドが少し早い気もしますが気のせいかもしれないレベルですね。

検証コード 2

object 型の変数を ToString() で文字列にしてメソッド呼び出ししてみます。

namespace ClassLibrary1
{
    public static class MethodExtensions
    {
        public static int ExtensionMethod(this object s) => 0;
    }
}
using ClassLibrary1;

BenchmarkDotNet.Running.BenchmarkRunner.Run<Sample>(new BenchmarkDotNet.Configs.DebugInProcessConfig());

public class Sample
{
    int[] ints = new int[short.MaxValue];
    object o = new object();
    object o_s = "";
    [BenchmarkDotNet.Attributes.Benchmark]
    public void MethodObject() { foreach (var c in ints) o.ExtensionMethod(); }
    [BenchmarkDotNet.Attributes.Benchmark]
    public void MethodObjectToString() { foreach (var c in ints) o.ToString()!.ExtensionMethod(); }
    [BenchmarkDotNet.Attributes.Benchmark]
    public void MethodObjectString() { foreach (var c in ints) o_s.ExtensionMethod(); }
    [BenchmarkDotNet.Attributes.Benchmark]
    public void MethodObjectStringToString() { foreach (var c in ints) o_s.ToString()!.ExtensionMethod(); }
}

実行

1 回目

Method Mean Error StdDev
MethodObject 11.93 us 0.238 us 0.551 us
MethodObjectToString 334.09 us 6.545 us 8.737 us
MethodObjectString 10.62 us 0.205 us 0.219 us
MethodObjectStringToString 10.54 us 0.184 us 0.172 us

2 回目

Method Mean Error StdDev
MethodObject 12.15 us 0.242 us 0.391 us
MethodObjectToString 325.80 us 6.365 us 10.809 us
MethodObjectString 11.33 us 0.223 us 0.298 us
MethodObjectStringToString 11.37 us 0.223 us 0.305 us

結果

やはり object 型のオブジェクトが遅いようですね。他は誤差のレベル、というか実行ごとに前後するレベルです。

まとめ

誤差レベルですが、やはり int や string を直接引数の型にできるならそうしておいた方が無難そうです。ただ最強は ref 値型なのだと思うのでまた後日動かしてみたいです。