web-dev-qa-db-ja.com

なぜC#で "in"パラメーター修飾子を使用するのでしょうか?

したがって、私はinパラメーター修飾子が何をするかを理解します(私は考えます)。しかし、それは非常に冗長であるように見えます。

通常、refを使用する唯一の理由は、inによって明示的に禁止されている呼び出し変数を変更することだと思います。したがって、in参照による受け渡しは、値による受け渡しと論理的に同等と思われます。

何らかのパフォーマンス上の利点はありますか?物事のバックエンド側では、refパラメーターは少なくとも変数の物理アドレスをコピーする必要があり、これは一般的なオブジェクト参照と同じサイズでなければならないというのが私の信念でした。

それでは、大きな構造体だけで利点がありますか、それとも他の場所で魅力的なコンパイラの最適化がありますか?後者の場合、なぜeveryパラメーターをinにしないのですか?

48
Travis Reed

inは最近C#言語に導入されました。

inは実際にはref readonlyです。一般的に、inが役立つユースケースは1つだけです。つまり、大量のreadonly structsを処理する高性能アプリです。

あなたが持っていると仮定:

readonly struct VeryLarge
{
    public readonly long Value1;   
    public readonly long Value2;

    public long Compute() { }
    // etc
}

そして

void Process(in VeryLarge value) { }

その場合、VeryLargeメソッドでこの構造体を使用するとき(たとえば、value.Compute()を呼び出すとき)にProcess構造体は防御コピーを作成せずに参照渡しされます。コンパイラによって保証されます。

読み取り専用でないstructin修飾子と共に渡すと、コンパイラーは構造体の呼び出し時に防御コピーを作成する上記のProcessメソッドでメソッドとアクセスプロパティを使用すると、パフォーマンスに悪影響を及ぼします。

本当に良い MSDNブログエントリ がありますので、注意深く読むことをお勧めします。

in- introducingの歴史的背景を取得したい場合は、C#言語のGitHubリポジトリで discussion を読むことができます。

一般に、ほとんどの開発者は、inの導入は間違いと見なされる可能性があることに同意します。これはかなりエキゾチックな言語機能であり、高性能のEdgeの場合にのみ有用です。

51
dymanoid

in参照による受け渡しは、値による受け渡しと論理的に同等と思われます。

正しい。

何らかのパフォーマンス上の利点はありますか?

はい。

物事のバックエンド側では、refパラメーターは少なくとも変数の物理アドレスをコピーする必要があり、これは通常のオブジェクト参照と同じサイズでなければならないというのが私の信念でした。

オブジェクトへの参照と変数への参照の両方が同じサイズであるという要件がなく、要件のいずれかがサイズであるマシンワードですが、実際には、32ビットマシンでは32ビット、64ビットマシンでは64ビットです。

「物理アドレス」がそれと関係があると思うことは私には不明です。 Windowsでは、物理アドレスではなく、仮想アドレスを使用します。ユーザーモードコード。物理アドレスがC#プログラムで意味があると考えられる状況を想像してみてください。知りたいです。

requirementがなく、あらゆる種類の参照がストレージの仮想アドレスとして実装されます。参照は、CLI仕様の準拠する実装のGCテーブルへの不透明なハンドルにすることができます。

大きな構造体だけで利点はありますか?

大きな構造体を渡すコストの削減は、機能の動機付けシナリオです。

inはプログラムを実際に高速化する保証はなく、プログラムを低速化する可能性があることに注意してください。 パフォーマンスに関するすべての質問は、経験的調査によって回答する必要がありますalways wins;である最適化はほとんどありません。これは「常に勝つ」最適化ではありません。

他の場所で魅力的なコンパイラの最適化がありますか?

コンパイラとランタイムはpermittedであり、C#仕様の規則に違反しない場合に選択した最適化を行います。私の知る限り、inパラメーターの最適化はまだ行われていませんが、将来このような最適化が妨げられることはありません。

なぜすべてのパラメーターを入力してはいけないのですか?

さて、in intパラメーターではなくintパラメーターを作成したとします。どのような費用がかかりますか?

  • コールサイトには、valueではなくvariableが必要になりました
  • 変数を登録できません。ジッタの慎重に調整されたレジスタ割り当てスキームには、レンチが投げ込まれました。
  • 呼び出しサイトのコードは、変数への参照を取得してスタックに配置する必要があるため、コードが大きくなりますが、それ以前は単純に値を呼び出しスタックにプッシュすることができました
  • コードが大きくなるということは、一部のショートジャンプ命令がロングジャンプ命令になった可能性があることを意味するため、コードも大きくなりました。これは、あらゆる種類のものにノックオン効果をもたらします。キャッシュはすぐに満杯になり、ジッターはより多くの作業を行う必要があり、ジッターはより大きなコードサイズで特定の最適化を行わないことを選択する場合があります。
  • 呼び出し先サイトでは、スタック(またはレジスタ)の値へのアクセスを、ポインターへの間接化に変更しました。現在、そのポインターはキャッシュ内にある可能性が非常に高いですが、それでも、値への1命令アクセスを2命令アクセスに変更しました。
  • 等々。

doubleであり、in doubleに変更するとします。この場合も、変数を高性能浮動小数点レジスターに登録することはできません。これはパフォーマンスの意味を持つだけでなく、プログラムの動作を変更することもできます! C#は、64ビットより高い精度で浮動小数点演算を行うことが許可されており、通常、浮動小数点を登録できる場合にのみそうします。

これは無料の最適化ではありません。代替と比較してパフォーマンスを測定する必要があります。設計ガイドラインが示唆するように、最初に大きな構造体を作成しないことが最善策です。

20
Eric Lippert

がある。 structを渡す場合、inキーワードを使用すると、メソッドがコンテンツを変更するリスクなしに、コンパイラーがポインターのみを渡す必要がある最適化が可能になります。最後は重要です—コピー操作を回避します。大きな構造体では、これは違いの世界を作ることができます。

6
TomTom

これは、関数型プログラミングのアプローチのために行われます。主要な原則の1つは、関数に副作用がないことです。つまり、関数はパラメーターの値を変更してはならず、値を返す必要があります。 C#では、値の変更を許可する参照のみによってコピーされることなく、構造体(および値の型)を渡す方法はありませんでした。 Swiftには、メソッドが値を変更し始める限り、構造体(それらのコレクションは構造体BTW)をコピーするハッキングアルゴリズムがあります。 Swiftを使用している人は、すべてコピーのものを認識していません。メモリ効率が良く明示的であるため、これはNice c#機能です。 what's new を見ると、スタック内の構造体と配列の周りでますます多くのことが行われていることがわかります。そして、これらの機能にはinステートメントが必要です。他の回答で言及されている制限はありますが、.netがどこに向かっているのかを理解するのにそれほど重要ではありません。

1
Access Denied