C#の出力パラメーターには、知っておくべきパフォーマンスへの影響がありますか?(例外のように)
out
つまり、1秒間に数百万回実行されるループ内のパラメーターを持つメソッドを用意するのは良い考えですか?
私はそれが醜いことを知っていますが、私はそれらを使用しているのと同じ方法でそれを使用しています-いくつかの検証が成功したかどうかを示すためにをInt32.TryParse
返し、成功した場合はいくつかの追加データを含むパラメーターを持っています。bool
out
C#の出力パラメーターには、知っておくべきパフォーマンスへの影響がありますか?(例外のように)
out
つまり、1秒間に数百万回実行されるループ内のパラメーターを持つメソッドを用意するのは良い考えですか?
私はそれが醜いことを知っていますが、私はそれらを使用しているのと同じ方法でそれを使用しています-いくつかの検証が成功したかどうかを示すためにをInt32.TryParse
返し、成功した場合はいくつかの追加データを含むパラメーターを持っています。bool
out
パラメータを使用することでパフォーマンスが大幅に低下することはないと思いますout
。何らかの形で発信者に情報を返さなければなりません -out
それを行う方法が異なるだけです。メソッド内で out パラメーターを広範囲に使用すると、アクセスごとに余分なレベルのリダイレクトが発生する可能性があるため、何らかのペナルティが発生する可能性があります。しかし、私はそれが重要であるとは思っていません。通常、最も読みやすいコードを記述し、さらに最適化を試みる前に、パフォーマンスが十分に優れているかどうかをテストします。
編集: これの残りの部分は事実上、余談です。とにかく、通常は避けるべき大きな値の型にのみ関連します:)
ただし、「32ビットを超えるすべてのタイプの戻り値は、とにかくマシンレベルのout引数と同様または同一に処理される」というKonradの主張には同意しません。ここに小さなテストアプリがあります:
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
struct BigStruct
{
public Guid guid1, guid2, guid3, guid4;
public decimal dec1, dec2, dec3, dec4;
}
class Test
{
const int Iterations = 100000000;
static void Main()
{
decimal total = 0m;
// JIT first
ReturnValue();
BigStruct tmp;
OutParameter(out tmp);
Stopwatch sw = Stopwatch.StartNew();
for (int i=0; i < Iterations; i++)
{
BigStruct bs = ReturnValue();
total += bs.dec1;
}
sw.Stop();
Console.WriteLine("Using return value: {0}",
sw.ElapsedMilliseconds);
sw = Stopwatch.StartNew();
for (int i=0; i < Iterations; i++)
{
BigStruct bs;
OutParameter(out bs);
total += bs.dec1;
}
Console.WriteLine("Using out parameter: {0}",
sw.ElapsedMilliseconds);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static BigStruct ReturnValue()
{
return new BigStruct();
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void OutParameter(out BigStruct x)
{
x = new BigStruct();
}
}
結果:
Using return value: 11316
Using out parameter: 7461
基本的に、out パラメーターを使用することで、データを小さなメソッドのスタック フレームに書き込んでから Main メソッドのスタック フレームにコピーするのではなく、データを最終的な宛先に直接書き込みます。
ただし、ベンチマーク アプリを自由に批判してください。何か見落としている可能性があります。
パフォーマンスへの影響はありません。out
技術的な観点からは、基本的に古い引数の受け渡しと同じです。膨大な量のデータがコピーされることはもっともらしく聞こえるかもしれませんが(たとえば、大きな構造体の場合)、これは実際には戻り値の場合と同じです。
実際、32ビットを超えるすべてのタイプの戻り値は、とにかくマシンレベルout
の引数と同様に処理されます。
out
最後のステートメントは、.NETでvalue==パラメーターを返すことを示唆していないことに注意してください。Jonのベンチマークは、これが明らかに(そして残念ながら)そうではないことを示しています。実際、それを同一にするために、名前付き戻り値の最適化がC++コンパイラーで採用されています。JITの将来のバージョンでは、大きな構造を返すパフォーマンスを向上させるために、同様のことが行われる可能性があります(ただし、.NETでは大きな構造は非常にまれであるため、これは不要な最適化になる可能性があります)。
ただし、(x86アセンブリに関する私の知識は非常に限られていますが)、関数呼び出しからオブジェクトを返すには、通常、呼び出しサイトに十分なスペースを割り当て、スタック上のアドレスをプッシュし、戻り値をコピーして埋める必要があります。これは基本的に同じですout
が、ターゲットメモリの場所に直接アクセスできるため、値の不要な一時コピーが省略されるだけです。
パフォーマンスの問題ではありませんが、以前に発生したものです - C# 4.0 ではバリアントを使用できません。
個人的には、私は自分のプライベートコード (つまり、別の型を使用せずに複数の値を返すメソッドを持つクラス内)out
でかなりの量のパラメーターを使用する傾向がありますが、パターンを除いて、パブリック API ではそれらを避ける傾向があります。bool Try{Something}(out result)
パラメータを回避する主な理由は、パフォーマンスではなく、コードの可読性です。
値型の場合はとにかく実際の違いはなく(常にコピーされます)、参照型の場合は基本的にrefを渡すのと同じです。
10回のうち9回は、outパラメータを使用するよりも、独自のダムレコードクラスを作成する方がよいでしょう。これは、後でコードに戻ったときに読み、理解するのが簡単です。
出力パラメータはrefによって渡されます。したがって、ポインタのみがスタックに渡されます。
値型が大きい場合、コピーは少なくなりますが、変数を使用するたびにポインターを逆参照する必要があります。
outパラメータを使用しても、パフォーマンスは低下しません。Outパラメータは基本的に参照パラメータであるため、呼び出し元と呼び出し先の両方が同じメモリを指します。