この質問はパフォーマンスのみに関連していることに注意してください。設計ガイドライン、哲学、互換性、移植性、および純粋なパフォーマンスに関係のないものはすべてスキップします。ありがとうございました。
質問に移ります。 C#のgetter/setterは実際には変装したメソッドであるため、パブリックフィールドの読み取りはgetterを呼び出すよりも高速である必要があると常に考えていました。
そのため、テストを行ったことを確認するために(以下のコード)。ただし、このテストは期待される結果のみを生成します(つまり、フィールドは34%でゲッターよりも高速です)if Visual Studio内から実行します。
コマンドラインから実行すると、ほぼ同じタイミングが表示されます...
唯一の説明は、CLRが追加の最適化を行うことです(ここで間違っている場合は修正してください)。
これらのプロパティがより洗練された方法で使用される実際のアプリケーションでは、同じ方法で最適化されるとは思いません。
現実の特性はフィールドよりも遅いという考えを証明または反証するのを手伝ってください。
質問は-パブリックフィールドがゲッターを上回るようにテストクラスを変更してCLRの動作を変更する方法です。ORは、内部ロジックのないプロパティがフィールドと同じ(少なくともゲッター上)
編集:リリースx64ビルドについてのみ話している
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace PropertyVsField
{
class Program
{
static int LEN = 20000000;
static void Main(string[] args)
{
List<A> a = new List<A>(LEN);
List<B> b = new List<B>(LEN);
Random r = new Random(DateTime.Now.Millisecond);
for (int i = 0; i < LEN; i++)
{
double p = r.NextDouble();
a.Add(new A() { P = p });
b.Add(new B() { P = p });
}
Stopwatch sw = new Stopwatch();
double d = 0.0;
sw.Restart();
for (int i = 0; i < LEN; i++)
{
d += a[i].P;
}
sw.Stop();
Console.WriteLine("auto getter. {0}. {1}.", sw.ElapsedTicks, d);
sw.Restart();
for (int i = 0; i < LEN; i++)
{
d += b[i].P;
}
sw.Stop();
Console.WriteLine(" field. {0}. {1}.", sw.ElapsedTicks, d);
Console.ReadLine();
}
}
class A
{
public double P { get; set; }
}
class B
{
public double P;
}
}
他の人がすでに述べたように、ゲッターはインラインです。
インライン化を避けたい場合は、
自動プロパティを手動プロパティに置き換えます。
class A
{
private double p;
public double P
{
get { return p; }
set { p = value; }
}
}
コンパイラにゲッターをインライン化しないように指示します(または、そのように感じた場合は両方とも)。
[MethodImpl(MethodImplOptions.NoInlining)]
get { return p; }
最初の変更ではパフォーマンスに違いはありませんが、2番目の変更では明確なメソッド呼び出しのオーバーヘッドが示されることに注意してください。
手動プロパティ:
auto getter. 519005. 10000971,0237547.
field. 514235. 20001942,0475098.
ゲッターのインライン化なし:
auto getter. 785997. 10000476,0385552.
field. 531552. 20000952,077111.
MSDNのVBチームメンバーの1人が書いた Properties vs Fields – Why Does It Matter?(Jonathan Aneja)) ブログ記事をご覧ください。対フィールド引数、および次のように簡単なプロパティについても説明します。
プロパティよりもフィールドを使用することについて聞いた議論の1つは、「フィールドの方が速い」ということですが、CLRのJust-In-Time(JIT)コンパイラーがプロパティアクセスをインライン化し、フィールドに直接アクセスするのと同じくらい効率的です。
JITは、内部メトリックがより高速なインライン化を決定する(ゲッターだけでなく)任意のメソッドをインライン化します。標準プロパティがreturn _Property;
あらゆる場合にインライン化されます。
異なる動作が見られるのは、デバッガーが接続されたデバッグモードでは、JITが大幅にハンディキャップを持ち、スタックの場所がコードから予想されるものと一致するようにするためです。
また、パフォーマンスの最大のルールを忘れて、ビートの思考をテストします。例えば、クイックソートは挿入ソートよりも漸近的に高速ですが、実際には非常に小さな入力の場合、挿入ソートは高速です。
唯一の説明は、CLRが追加の最適化を行うことです(ここで間違っている場合は修正してください)。
はい、インライン化と呼ばれます。これはコンパイラーで実行されます(マシンコードレベル-つまりJIT)。ゲッター/セッターは取るに足らない(つまり非常に単純なコード)ため、メソッド呼び出しは破棄され、ゲッター/セッターは周囲のコードで記述されます。
これは、デバッグ(つまり、getterまたはsetterにブレークポイントを設定する機能)をサポートするためのデバッグモードでは発生しません。
Visual Studioでは、デバッガーでこれを行う方法はありません。リリースをコンパイルし、デバッガをアタッチせずに実行すると、完全な最適化が得られます。
これらのプロパティがより洗練された方法で使用される実際のアプリケーションでは、同じ方法で最適化されるとは思いません。
世界は間違った幻想に満ちています。それらはまだ簡単なため最適化されます(つまり、単純なコードなので、インライン化されます)。
Visual Studioで「実際の」パフォーマンスを確認できることに注意してください。
デバッガーが接続されていても、ジッターされたアセンブリは同じになり、必要に応じて最適化された逆アセンブリを実行できます。これは、CLRがコードを最適化する方法を理解するために不可欠です。
すべての記事を読んだ後、これらのコードでベンチマークを作成することにしました。
[TestMethod]
public void TestFieldVsProperty()
{
const int COUNT = 0x7fffffff;
A a1 = new A();
A a2 = new A();
B b1 = new B();
B b2 = new B();
C c1 = new C();
C c2 = new C();
D d1 = new D();
D d2 = new D();
Stopwatch sw = new Stopwatch();
long t1, t2, t3, t4;
sw.Restart();
for (int i = COUNT - 1; i >= 0; i--)
{
a1.P = a2.P;
}
sw.Stop();
t1 = sw.ElapsedTicks;
sw.Restart();
for (int i = COUNT - 1; i >= 0; i--)
{
b1.P = b2.P;
}
sw.Stop();
t2 = sw.ElapsedTicks;
sw.Restart();
for (int i = COUNT - 1; i >= 0; i--)
{
c1.P = c2.P;
}
sw.Stop();
t3 = sw.ElapsedTicks;
sw.Restart();
for (int i = COUNT - 1; i >= 0; i--)
{
d1.P = d2.P;
}
sw.Stop();
t4 = sw.ElapsedTicks;
long max = Math.Max(Math.Max(t1, t2), Math.Max(t3, t4));
Console.WriteLine($"auto: {t1}, {max * 100d / t1:00.00}%.");
Console.WriteLine($"field: {t2}, {max * 100d / t2:00.00}%.");
Console.WriteLine($"manual: {t3}, {max * 100d / t3:00.00}%.");
Console.WriteLine($"no inlining: {t4}, {max * 100d / t4:00.00}%.");
}
class A
{
public double P { get; set; }
}
class B
{
public double P;
}
class C
{
private double p;
public double P
{
get => p;
set => p = value;
}
}
class D
{
public double P
{
[MethodImpl(MethodImplOptions.NoInlining)]
get;
[MethodImpl(MethodImplOptions.NoInlining)]
set;
}
}
デバッグモードでテストすると、次の結果が得られました。
auto: 35142496, 100.78%.
field: 10451823, 338.87%.
manual: 35183121, 100.67%.
no inlining: 35417844, 100.00%.
しかし、リリースモードに切り替えると、結果は以前とは異なります。
auto: 2161291, 873.91%.
field: 2886444, 654.36%.
manual: 2252287, 838.60%.
no inlining: 18887768, 100.00%.
自動プロパティがより良い方法のようです。