19

object.ReferenceEqualsメソッドを使用する場合と使用する場合に余分なオーバーヘッドはあり((object)obj1 == (object)obj2)ますか?

最初のケースでは、静的メソッド呼び出しが関係し、どちらの場合もオブジェクトへの何らかの形式のキャストが関係します。

コンパイラがこれらの方法のバランスを取ったとしても、不等式はどうなるでしょうか?

(object)obj != null

と比較して...

!object.ReferenceEquals(obj,null)

ある時点で、!= 演算子内で、または ReferenceEquals メソッドの結果に適用されると、論理否定が発生すると思います。どう思いますか?

考慮すべき読みやすさの問題もあります。ReferenceEquals は、等値をチェックする場合により明確に見えますが、不等値の場合、!前の を見逃す可能性がobject.ReferenceEqualsあります!=が、最初のバリエーションの は見逃せません。

4

6 に答える 6

22

object.ReferenceEquals メソッドを使用する際に余分なオーバーヘッドはありますか

いいえ。メソッドには、参照の等価性チェックを実行するための最小限の IL 記述が直接含まれており (記録: VB の演算子と同等ですIs)、JIT によってインライン化されることが多いため (特に x64 をターゲットにする場合)、オーバーヘッドはありません。

object.ReferenceEquals読みやすさについて: 個人的には、セマンティクスを明示的に表現しているため、(否定形式であっても) 読みやすい可能性があると思います。to へのキャストobjectは、一部のプログラマーを混乱させる可能性があります。

これについて議論している記事を見つけました。(object)x == yIL フットプリントが小さいため、優先されます。Xこれにより、この比較を使用してメソッドのインライン化が容易になる可能性があると主張しています。ただし(JITの詳細な知識はありませんが、論理的かつ直感的に)これは間違っていると思います.JITが最適化C ++コンパイラのように動作する場合、への呼び出しをインライン化したReferenceEqualsにメソッドを考慮するため、(メソッドをインライン化するため)X) メモリ フットプリントはどちらの方法でもまったく同じになります。

つまり、どちらか一方を選択しても、JIT にはまったく影響がなく、結果としてパフォーマンスにも影響しません。

于 2009-04-09T19:21:01.037 に答える
5

ここでの回答とは対照的に、私は(object) ==より速く見つけましobject.ReferenceEqualsた。どれだけ速いかというと、ごくわずかです!

テスト ベッド:

参照の等価性チェックが必要なのはわかっていますobject.Equals(,)が、オーバーライドされていないクラスの場合に備えて、静的メソッドも含めています。

プラットフォーム: x86; 構成: リリース ビルド

class Person {
}

public static void Benchmark(Action method, int iterations = 10000)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < iterations; i++)
        method();

    sw.Stop();
    MsgBox.ShowDialog(sw.Elapsed.TotalMilliseconds.ToString());
}

テスト:

Person p1 = new Person();
Person p2 = new Person();
bool b;
Benchmark(() =>
{
    b = (object)p1 == (object)p2; //960 ~ 1000ms
    b = object.ReferenceEquals(p1, p2); //~ 1250ms
    b = object.Equals(p1, p2); //2100ms
    b = EqualityComparer<Person>.Default.Equals(p1, p2); //~4000ms

}, 100000000);

Person p1 = new Person();
Person p2 = null;
bool b;
Benchmark(() =>
{
    b = (object)p1 == (object)p2; //990 ~ 1000ms
    b = object.ReferenceEquals(p1, p2); // 1230 ~ 1260ms
    b = object.Equals(p1, p2); //1250 ~ 1300ms
    b = EqualityComparer<Person>.Default.Equals(p1, p2); //~3100ms

}, 100000000);

Person p1 = null;
Person p2 = null;
bool b;
Benchmark(() =>
{
    b = (object)p1 == (object)p2; //960 ~ 1000ms
    b = object.ReferenceEquals(p1, p2); //1260 ~ 1270ms
    b = object.Equals(p1, p2); //1180 ~ 1220ms
    b = EqualityComparer<Person>.Default.Equals(p1, p2); //~3100ms

}, 100000000);

Person p1 = new Person();
Person p2 = p1;
bool b;
Benchmark(() =>
{
    b = (object)p1 == (object)p2; //960 ~ 1000ms
    b = object.ReferenceEquals(p1, p2); //1260 ~ 1280ms
    b = object.Equals(p1, p2); //1150 ~ 1200ms
    b = EqualityComparer<Person>.Default.Equals(p1, p2); //3700 ~ 3800ms

}, 100000000);

object.Equals(,)内部で呼び出しReferenceEquals、それらが等しくない場合Equalsは、クラスのオーバーライドされた仮想メソッドを呼び出すため、速度の違いに気付くでしょう。

結果は構成でDebugも一貫していました...

指摘したように、読みやすさ/意味深さ/意図の明確化に重点を置く必要があります。

于 2012-12-16T08:28:22.943 に答える
3

非常に大きなコードベースで、パフォーマンスが重要なコードで何時間も遅くなり、非常に深い呼び出し深度が発生することもありました。

現実世界の「マイクロ ベンチマーク」の外では、JIT にはさらに多くの問題と懸念があり、コンパイル時の C++ WPO の贅沢も、C# コンパイラのより直接的な変換の容易さもありません。 C# コンパイラが完了した後、必然的にすべてのコンテキストを取得します。

「ペダンティック」フォーム:

if ((object)a == (object)b) { }     // ref equals

if (!((object)a == (object)b)) { }  // ref not equals

本当にパフォーマンスの問題を考慮して測定している場合、またはいくつかの非常に大きな普及クラスの JIT からプレッシャーを取り除く必要がある場合、これは非常に役立ちます。同じことが NullOrEmpty と '(object)str == null || にも当てはまります。str.Length == 0' .

WPO のような余裕がなく、多くの場合、JIT を実行した後にどのアセンブリがロードまたはアンロードされるかがわからないというすべての制約があり、何が最適化され、どのように最適化されるかに関して、奇妙な非決定論的なことが起こります。

これは大きなトピックですが、いくつかのポイントを次に示します。

  1. JIT はインライン化を追跡し、ダウン コールの深さの最適化を登録します。これは、その時点で他に何が起こっているかに完全に依存します。使用のためにチェーンの上流にある関数をコンパイルし、別の実行でチェーンの下流にある関数をコンパイルすることになった場合、大幅に異なる結果が得られる可能性があります。レイテンシーや UI ドリブンに縛られている多くのアプリケーションにとって最悪の事態は非決定論であり、大規模なアプリではこれがすぐに積み重なってしまう可能性があります。

  2. !((object)a == (object)b) と (object)a != (object)b は、!(a == b) と a != に確実に当てはまるように、常に同じコードにコンパイルされるとは限りません。 b、明示的な演算子や Equals オーバーライドがなくても。'(object)a != (object)b' は、非常にコストのかかるランタイムへの .Net 独自のよりペダンティックな呼び出しをトリガーする可能性がはるかに高くなります。

  3. '(object)' または 'RefEquals' を使用して早期かつ頻繁にガードすると、現在演算子または Equals のオーバーライドがなくても、JIT にとって非常に役立つ場合があります。(オブジェクト) はさらに優れています。型にオーバーライドがあるかどうかを判断するために JIT が何をしなければならないかを考え、ビザンチン (sp) 等価規則などに対処する場合、それは小さな地獄のようなものであり、後で公開される可能性のあるものであり、ref を意図しています。平等であるため、後で突然のスローダウンや不安定なコードから身を守ることができます。すでに公開されていて封印されていない場合、JIT は、ルールがそれらを追跡するか、または追跡する時間があることを保証できません。

  4. 同じルールと問題が一般的に適用されるため、OPの質問の一部ではありませんが、一般的により普及している「null」チェックを保護することはおそらくさらに重要です。'(object)a == null' および '!((object)a == null)' は、'ペダンティック' に相当します。

于 2012-12-26T21:22:35.653 に答える
3

Object.ReferenceEquals のオーバーヘッドは、ほとんどのシナリオで JITted される引数の読み込みのみです。その後、Object.ReferenceEquals と operator== の両方が 1 つの IL ceq 演算子になります。最悪の場合、違いはわずかです。

さらに重要なことに、Object.ReferenceEquals は (object)o1 == (object)o2 よりもはるかに意図を明らかにします。一連のキャストの下に意図を隠すのではなく、コードで「参照の等価性/同一性をテストしています」と明確に述べています。

于 2009-04-09T19:23:28.563 に答える
2

== 演算子のほうが優れているという前述の記事は、少なくとも .NET 4.0 では不完全な情報を提供しています (2.0 回で書き直されました)。

これは、「AnyCPU」構成でプロジェクトをビルドする場合にのみ当てはまります。'x86' または 'x64' に設定すると、(object)obj == null と ReferenceEquals(object, null) が同一の IL になり、両方のメソッドが単に 1 つの 'ceq' IL 命令になります。

答えは次のとおりです。両方の方法で生成された IL は、Release / x86 または x64 ビルドで同一です。

少なくとも私の好みでは、ReferenceEquals の方が間違いなく読みやすいです。

于 2011-02-13T22:04:30.213 に答える
0
public static bool ReferenceEquals (Object objA, Object objB) {
        return objA == objB;
    }

http://referencesource.microsoft.com/#mscorlib/system/object.cs,4d607d6d56a93c7e

于 2015-04-01T21:41:24.703 に答える