ゲーム開発に適用できるC#プロパティを使用することをマイクロソフトは推奨していますか?


26

次のようなプロパティが必要になることがあります:

public int[] Transitions { get; set; }

または:

[SerializeField] private int[] m_Transitions;
public int[] Transitions { get { return m_Transitions; } set { m_Transitions = value; } }

しかし、私の印象では、ゲーム開発では、特に理由がない限り、すべてを可能な限り高速にする必要があります。私が読んだことからの私の印象は、Unity 3Dがプロパティを介してインラインアクセスすることを確信していないため、プロパティを使用すると余分な関数呼び出しが発生することです。

パブリックフィールドを使用しないというVisual Studioの提案を常に無視する権利はありますか?(使用int Foo { get; private set; }目的を示すのに利点がある場合に使用することに注意してください。)

時期尚早な最適化が悪いことは承知していますが、私が言ったように、ゲーム開発では、同様の努力で高速化できる場合でも、速度を遅くすることはありません。


34
何かが「遅い」と信じる前に、常にプロファイラーで検証してください。何かが「遅い」と言われ、プロファイラーは0.5秒を失うために5億回実行する必要があると言った。フレーム内で数百回以上実行されることはないので、私はそれは無関係であると判断しました。
アルモ

5
あちこちで少し抽象化することで、低レベルの最適化をより広く適用できることがわかりました。たとえば、さまざまな種類のリンクリストを使用した多数の単純なCプロジェクトを見てきましたが、これらは連続した配列(例:のバッキングArrayList)に比べて非常に遅いです。これは、Cにはジェネリックがないため、これらのCプログラマは、ArrayListsリサイズアルゴリズムに対して同じことを行うよりも、リンクリストを繰り返し再実装する方が簡単だと思われるためです。優れた低レベルの最適化を思いついたら、それをカプセル化してください!
hegel5000

3
@Almo 10,000個の異なる場所で発生する操作に同じロジックを適用しますか?それはクラスメンバーアクセスだからです。論理的には、最適化のクラスは重要ではないと判断する前に、パフォーマンスコストに10Kを掛ける必要があります。0.5ミリ秒は、ゲームのパフォーマンスが0.5秒ではなく損なわれる場合のより良い目安です。あなたの主張は今でも変わりませんが、正しい行動はあなたが考えているほど明確ではないと思います。
ピオジョ

5
2頭の馬がいます。それらをレース。
マスト

@piojoしません。常にこれらのことを考えなければなりません。私の場合、UIコードのforループでした。1つのUIインスタンス、少数のループ、少数の反復。記録のために、私はあなたのコメントを支持しました。:)
アルモ

回答:


44

しかし、私の印象では、ゲーム開発では、特に理由がない限り、すべてを可能な限り高速にする必要があります。

必ずしも。アプリケーションソフトウェアと同じように、ゲームにはパフォーマンスが重要なコードとそうでないコードがあります。

コードがフレームごとに数千回実行される場合、そのような低レベルの最適化は意味があります。(最初に頻繁に呼び出す必要があるかどうかを最初に確認することもできますが、最も速いコードは実行しないコードです)

コードが数秒ごとに実行される場合、読みやすさと保守性はパフォーマンスよりもはるかに重要です。

私が読んだことは、Unity 3Dはプロパティを介してインラインアクセスすることを確信していないため、プロパティを使用すると余分な関数呼び出しが発生することです。

スタイルの簡単なプロパティを使用するint Foo { get; private set; }と、コンパイラがプロパティラッパーを最適化することを確信できます。しかし:

  • 実際に実装している場合、それ以上確実ではありません。その実装が複雑であればあるほど、コンパイラがインライン化できる可能性は低くなります。
  • プロパティは複雑さを隠すことができます。これは両刃の剣です。一方では、コードをより読みやすく、変更可能にします。しかし一方で、クラスのプロパティにアクセスする別の開発者は、単一の変数にアクセスしているだけで、そのプロパティの背後に実際に隠されているコードの量を認識していないと考えるかもしれません。プロパティが計算上高価になり始めると、私はそれを明示的なGetFoo()/ SetFoo(value)メソッドにリファクタリングする傾向があります。(または、他の人がヒントを得ることをさらに確実にする必要がある場合:CalculateFoo()/ SwitchFoo(value)

最上位レベルのオブジェクトが非読み取り専用クラスである場合、そのフィールドが構造内などの構造内にある場合でも、構造タイプの非読み取り専用パブリックフィールド内のフィールドへのアクセスは高速です。フィールドまたは非読み取り専用の独立変数。これらの条件が当てはまる場合、パフォーマンスに悪影響を与えることなく、構造を非常に大きくすることができます。このパターンを破ると、構造のサイズに比例して速度が低下します。
スーパーキャット

5
「その後、明示的なGetFoo()/ SetFoo(value)メソッドにリファクタリングする傾向があります。」-良い点は、プロパティからメソッドに重い操作を移動する傾向があることです。
マークロジャース

Unity 3Dコンパイラーが言及した最適化を行うことを確認する方法はありますか(AndroidのIL2CPPおよびMonoビルドで)?
ピオジョ

9
@piojoほとんどのパフォーマンスの質問と同様に、最も簡単な答えは「やるだけ」です。コードの準備ができました-フィールドを持つこととプロパティを持つことの違いをテストします。実装の詳細は、2つのアプローチの重要な違いを実際に測定できる場合にのみ調査する価値があります。私の経験では、すべてを可能な限り高速に記述する場合、利用可能な高レベルの最適化を制限し、低レベルの部分だけを競おうとします(「木の森を見ることができません」)。たとえば、Update完全にスキップする方法を見つけると、Update高速化するよりも時間を節約できます。
ルアーン

9

疑わしい場合は、ベストプラクティスを使用してください。

あなたは疑って​​います。


それは簡単な答えでした。もちろん、現実はもっと複雑です。

まず、ゲームプログラミングは超高性能プログラミングであり、すべてが可能な限り高速でなければならないという神話があります。ゲームプログラミングをパフォーマンスを意識したプログラミングに分類します。開発者はパフォーマンスの制約を認識し、その中で作業する必要があります。

第二に、すべてのものを可能な限り高速にすることがプログラムを高速に実行するという神話があります。実際には、ボトルネックを特定して最適化することがプログラムを高速化するものであり、コードの保守と変更が容易であれば、それははるかに簡単/安く/高速になります。極端に最適化されたコードは、維持および変更が困難です。したがって、非常に最適化されたプログラムを作成するには、必要に応じて頻繁に極端なコード最適化を使用する必要があります。

第三に、プロパティとアクセッサの利点は、変更に対して堅牢であるため、コードの保守と変更が容易になることです。これは、高速プログラムを作成するために必要なことです。誰かがあなたの値をnullに設定したいときに例外をスローしたいですか?セッターを使用します。値が変更されるたびに通知を送信したいですか?セッターを使用します。新しい値を記録しますか?セッターを使用します。遅延初期化を行いたいですか?ゲッターを使用します。

4番目に、個人的には、この場合のマイクロソフトのベストプラクティスに従っていません。取るに足らないパブリックゲッターとセッターを持つプロパティが必要な場合は、フィールドを使用し、必要に応じてプロパティに変更します。ただし、このような些細なプロパティが必要になることはほとんどありません。境界条件を確認したり、セッターをプライベートにしたりするのが一般的です。


2

.netランタイムが単純なプロパティをインライン化することは非常に簡単で、多くの場合そうします。しかし、このインライン化は通常、プロファイラーによって無効にされているため、誤解されやすく、公共財産を公共分野に変更するとソフトウェアが高速化すると考えられます。

コードの再コンパイル時に再コンパイルされない他のコードによって呼び出されるコードでは、プロパティを使用するのに適したケースがあります。フィールドからプロパティへの変更は重大な変更です。しかし、get / setにブレークポイントを設定できることを除いて、ほとんどのコードでは、パブリックフィールドと比較して単純なプロパティを使用することの利点は見たことがありません。

私がプロのC#プログラマーとして10年間でプロパティを使用した主な理由は、他のプログラマーがコーディング標準を守っていないと言って停止することでした。プロパティを使用しない正当な理由はほとんどなかったため、これは良いトレードオフでした。


2

構造体とネイキッドフィールドは、管理されていないAPIとの相互運用性を容易にします。多くの場合、低レベルAPIは参照によって値にアクセスしたいことがわかります。これはパフォーマンスに優れています(不要なコピーを避けるため)。プロパティを使用することはそれに対する障害であり、多くの場合、ラッパーライブラリは使いやすさのために、時にはセキュリティのためにコピーを行います。

そのため、プロパティを持たず、裸のフィールドを持つベクトルおよび行列タイプを使用すると、多くの場合パフォーマンスが向上します。


ベストプラクティスは、真空で作成されていません。いくつかのカルトカルトにもかかわらず、一般的にベストプラクティスは正当な理由で存在します。

この場合、いくつかあります:

  • プロパティを使用すると、クライアントコードを変更せずに実装を変更できます(バイナリレベルでは、ソースレベルでクライアントコードを変更せずにフィールドをプロパティに変更できますが、変更後に異なるものにコンパイルされます) 。つまり、最初からプロパティを使用することで、プロパティを内部で実行するためだけに、自分のものを参照するコードを再コンパイルする必要がなくなります。

  • タイプのフィールドの可能な値のすべてが有効な状態ではない場合、コードがそれを変更するクライアントコードにそれらを公開することは望ましくありません。したがって、値の組み合わせの一部が無効な場合、フィールドをプライベート(または内部)に維持する必要があります。

私はクライアントコードを言ってきました。それはあなたを呼び出すコードを意味します。ライブラリを作成していない場合(またはライブラリを作成しているが、パブリックの代わりに内部を使用している場合)、通常はそれといくつかの良い規律で逃げることができます。そのような状況では、プロパティを使用するベストプラクティスは、自分で足を撃たないようにすることです。さらに、他の場所で変更されているかどうかを心配する代わりに、単一のファイルでフィールドが変更できるすべての場所を見ることができれば、コードについて推論するのがはるかに簡単です。実際、プロパティは、何が悪いのかを理解するときにブレークポイントを置くのにも適しています。


はい、業界で行われていることを見ることは価値があります。ただし、ベストプラクティスに反する動機はありますか?それとも、ベストプラクティスに反するだけなのか、コードの推論を難しくするのか、他の誰かがやったというだけの理由なのでしょうか。ああ、ところで、「他の人がやる」というのは、あなたがカーゴカルトを始める方法です。


だから...あなたのゲームは遅いですか?ボトルネックを特定し、それを修正するために時間を割く方が、それが何であるかを推測するよりも優れています。コンパイラーが多くの最適化を行うので安心できます。そのため、間違った問題を見ている可能性があります。

反対に、最初に何をすべきかを決定する場合は、フィールドやプロパティなどの小さな詳細を心配するのではなく、最初にどのアルゴリズムとデータ構造を心配する必要があります。


最後に、ベストプラクティスに反して何かを獲得していますか?

あなたの詳細(AndroidのUnityとMono)について、Unityは参照によって値を取りますか?そうでない場合は、とにかく値をコピーしますが、パフォーマンスは向上しません。

存在する場合、このデータをrefを取るAPIに渡す場合。フィールドを公開するのは理にかなっていますか、それとも、APIを直接呼び出すことができるタイプにすることができますか?

はい、もちろん、裸のフィールドを持つ構造体を使用して行うことができる最適化があります。たとえば、ポインタ Span<T>やそれに類似したものでそれらにアクセスします。また、メモリ上でコンパクトであるため、ネットワーク経由で送信したり、永続的なストレージに入れたりするためにシリアル化するのが簡単になります(もちろんコピーです)。

さて、適切なアルゴリズムと構造を選択しましたが、それらがボトルネックであることが判明した場合、それを修正する最良の方法を決定します...それは裸のフィールドを持つ構造体であるかどうかです それが起こった場合、いつ起こるかを心配することができます。その間、プレイする価値のある楽しいゲームを作成するなど、より重要な事項について心配することができます。


1

...私の印象では、ゲーム開発では、特に理由がない限り、すべてを可能な限り高速にする必要があります。

時期尚早な最適化が悪いことは承知していますが、私が言ったように、ゲーム開発では、同様の努力で高速化できる場合でも、速度を遅くすることはありません。

時期尚早な最適化は悪いことですが、それはストーリーの半分にすぎないことを理解するのは正しいことです(以下の完全な引用を参照)。ゲーム開発においてさえ、すべてが可能な限り高速である必要はありません

最初に動作させます。その後、必要なビットを最適化します。これは、最初のドラフトが乱雑であるか、不必要に非効率的である可能性があるということではありませんが、この段階では最適化は優先事項ではありません。

本当の問題は、プログラマーが間違った場所で間違ったタイミングで効率を心配することに非常に多くの時間を費やしていることです。早すぎる最適化はプログラミングのすべての悪(または少なくともその大部分)の根源です」ドナルドクヌース、1974年。


1

そのような基本的なものについては、C#オプティマイザーがとにかくそれを正しくします。あなたがそれを心配するなら(そしてあなたがあなたのコードの1%以上についてそれを心配するなら、あなたはそれを間違っています)、あなたのために属性が作成されました。この属性[MethodImpl(MethodImplOptions.AggressiveInlining)]により、メソッドがインライン化される可能性が大幅に増加します(プロパティゲッターとセッターはメソッドです)。


0

構造型のメンバをフィールドではなくプロパティとして公開すると、特に構造が「風変わりなオブジェクト」ではなく構造として実際に扱われる場合、パフォーマンスとセマンティクスが低下することがよくあります。

.NETの構造は、一緒にダクトでテーピングされたフィールドのコレクションであり、いくつかの目的のためにユニットとして扱うことができます。.NET Frameworkは、クラスオブジェクトとほぼ同じ方法で構造を使用できるように設計されており、便利な場合があります。

構造を取り巻くアドバイスの多くは、プログラマが構造をフィールドの集合としてではなくオブジェクトとして使用したいという考えに基づいています。一方、テープでまとめられたフィールドのように動作するものがあると便利な場合が多くあります。それが必要な場合、.NETのアドバイスはプログラマーとコンパイラーに不必要な作業を追加し、構造を直接使用するよりもパフォーマンスとセマンティクスを悪化させます。

構造体を使用する際に留意すべき最大の原則は次のとおりです。

  1. 構造体を値で渡したり戻したりしないでください。そうしないと、不必要にコピーされてしまいます。

  2. 原則#1が順守されている場合、大きな構造は小さな構造と同様に機能します。

場合はAlphablob26個の公共含む構造であるintAZという名前のフィールドを、およびプロパティのゲッターabその和を返すabフィールドを、そして与えられAlphablob[] arr; List<Alphablob> list;、コードは、int foo = arr[0].ab + list[0].ab;フィールドを読む必要があるだろうabarr[0]、しかし、すべての26の分野読む必要があるでしょうlist[0]でもそれは意志かかわらをそれらの2つを除くすべてを無視します。のような構造で効率的に動作できる一般的なリストのようなコレクションがalphaBlob必要な場合は、インデックス付きゲッターをメソッドに置き換える必要があります。

delegate ActByRef<T1,T2>(ref T1 p1, ref T2 p2);
actOnItem<TParam>(int index, ActByRef<T, TParam> proc, ref TParam param);

それは次に起動しproc(ref backingArray[index], ref param);ます。1置き換えた場合には、そのようなコレクションを考えるsum = myCollection[0].ab;

int result;
myCollection.actOnItem<int>(0, 
  ref (ref alphaBlob item, ref int dest)=>dest = item,
  ref result);

これによりalphaBlob、プロパティゲッターで使用されない部分をコピーする必要がなくなります。渡されたデリゲートはそのrefパラメータ以外にはアクセスしないため、コンパイラは静的デリゲートを渡すことができます。

残念ながら、.NET Frameworkは、このことをうまく機能させるために必要なデリゲートの種類を定義しておらず、構文は最終的には混乱の種になります。一方、このアプローチにより、構造を不必要にコピーすることを回避できます。これにより、配列に格納された大きな構造に対してインプレースアクションを実行することが実用的になります。これはストレージにアクセスする最も効率的な方法です。


こんにちは、間違ったページに回答を投稿しましたか?
ピオジョ

@pojo:おそらく、答えの目的は、フィールドではなくプロパティを使用するのが悪い状況を特定し、アクセスをカプセル化しながら、フィールドのパフォーマンスとセマンティックの利点を達成する方法を特定することであることを明確にすべきでした。
スーパーキャット

@piojo:私の編集は物事を明確にしますか?
スーパーキャット

「list [0]の26個のフィールドすべてを読み取る必要がありますが、2つを除くすべてのフィールドを無視します。」何?本気ですか?MSILを確認しましたか?C#はほとんどの場合、クラス型を参照渡しします。
pjc50

@ pjc50:プロパティを読み取ると、値による結果が得られます。インデックス付きゲッターlistとプロパティゲッターのab両方がインライン化されている場合、JITterがメンバーのみを必要abすることを認識する可能性がありますが、実際にそのようなことを行うJITterバージョンは知りません。
スーパーキャット
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.