C#では、UInt32とInt32を使用するとパフォーマンスに大きな違いがあります
-
08-07-2019 - |
質問
既存のアプリケーションをC#に移植していますが、可能な限りパフォーマンスを改善したいと考えています。多くの既存のループカウンターと配列参照は、使用するInt32ではなくSystem.UInt32として定義されています。
UInt32とInt32を使用した場合、パフォーマンスに大きな違いはありますか?
解決
プロセッサレベルでの符号付き算術と符号なし算術の違いの可能性以外に、パフォーマンスに関する考慮事項はないと思いますが、その時点では違いは無意味だと思います。
大きな違いは、すべての言語でサポートされていないため、符号なしの型はCLSに準拠していないため、CLSに準拠していることです。
他のヒント
短い答えは"いいえ。パフォーマンスへの影響は無視できます」。
正解は「それは依存します」です
より良い質問は、「サインが必要ないことが確実なときにuintを使用すべきですか?」
決定的な「はい」を指定できない理由または「いいえ」パフォーマンスに関しては、ターゲットプラットフォームが最終的にパフォーマンスを決定するためです。つまり、パフォーマンスは、コードを実行するプロセッサーと使用可能な命令によって決まります。 .NETコードは Intermediate Language (ILまたはバイトコード)にコンパイルされます。これらの指示は、 Just-In-Time (JIT)コンパイラーは共通言語ランタイム(CLR)。すべてのユーザーに対して生成されるコードを制御または予測することはできません。
ハードウェアがパフォーマンスの最終的な調停者であることを知っていると、「。NETが符号付き整数と符号なし整数で生成するコードの違いは?」および"違いは私のアプリケーションとターゲットプラットフォームに影響しますか?"
これらの質問に答える最良の方法は、テストを実行することです。
class Program
{
static void Main(string[] args)
{
const int iterations = 100;
Console.WriteLine(Signed: 00:00:00.5066966
Unsigned: 00:00:00.5052279
quot;Signed: {Iterate(TestSigned, iterations)}");
Console.WriteLine(<*>quot;Unsigned: {Iterate(TestUnsigned, iterations)}");
Console.Read();
}
private static void TestUnsigned()
{
uint accumulator = 0;
var max = (uint)Int32.MaxValue;
for (uint i = 0; i < max; i++) ++accumulator;
}
static void TestSigned()
{
int accumulator = 0;
var max = Int32.MaxValue;
for (int i = 0; i < max; i++) ++accumulator;
}
static TimeSpan Iterate(Action action, int count)
{
var elapsed = TimeSpan.Zero;
for (int i = 0; i < count; i++)
elapsed += Time(action);
return new TimeSpan(elapsed.Ticks / count);
}
static TimeSpan Time(Action action)
{
var sw = new Stopwatch();
sw.Start();
action();
sw.Stop();
return sw.Elapsed;
}
}
2つのテストメソッド TestSigned および TestUnsigned は、それぞれ、符号付き整数と符号なし整数に対して、それぞれ単純な増分で最大200万回の反復を実行します。テストコードは、各テストを100回繰り返し、結果を平均します。これにより、潜在的な矛盾を排除する必要があります。 x64用にコンパイルされたi7-5960Xの結果は次のとおりです。
<*>これらの結果はほぼ同じですが、明確な答えを得るには、プログラム用に生成されたバイトコードを確認する必要があります。 ILDASM .NET SDKの一部として、コンパイラによって生成されたアセンブリ内のコードを検査します。
ここで、C#コンパイラは符号付き整数を優先し、実際にはほとんどの操作を符号付き整数としてネイティブに実行し、ブランチ(a.k.a jumpまたはif)を比較するときにメモリ内の値を符号なしとしてのみ扱うことがわかります。 TestUnsigned のイテレータとアキュムレータの両方に符号なし整数を使用しているという事実にもかかわらず、コードは単一の命令を除いて TestSigned メソッドとほぼ同じです。 IL_0016 。 ECMA仕様では、違いが説明されています。
blt.un.s: 短い形式(符号なしまたは順序なし)の場合、ターゲットに分岐します。
blt.s: 短い場合はターゲットに分岐します。
このような一般的な命令であるため、ほとんどの最新のハイパワープロセッサには両方の操作のハードウェア命令があり、同じサイクル数で実行される可能性が高いと想定しても安全ですが、これは保証されていません。低電力プロセッサでは、命令数が少なく、unsigned intの分岐がない場合があります。この場合、JITコンパイラーは、 blt.un.s IL命令を実行するために、複数のハードウェア命令(たとえば、変換、次に分岐など)を発行する必要がある場合があります。この場合でも、これらの追加の指示は基本的なものであり、おそらくパフォーマンスには影響しません
.NETでこの問題について調査したことはありませんが、Win32 / C ++の昔、「signed int」をキャストしたい場合は、 「signed long」にすると、CPUは符号を拡張するためにopを実行する必要がありました。 &quot; unsigned int&quot;をキャストするには「unsigned long」には、上位バイトにゼロが入っているだけです。節約は数クロックサイクルのオーダーでした(つまり、知覚可能な差を得るには何十億回もする必要があります)。
パフォーマンスに関しては、違いはありません。単純な整数計算はよく知られており、現代のCPUはそれらを迅速に実行するために高度に最適化されています。
これらのタイプの最適化は、努力する価値はほとんどありません。タスクに最適なデータ型を使用し、そのままにしておきます。これがデータベースに触れるだけであれば、おそらくDB設計、クエリ構文、またはインデックス付け戦略で、C#のコード最適化を数百桁相殺する12の調整を見つけることができます。
どちらの方法でも同じ量のメモリを割り当てます(ただし、符号用のスペースを節約しないため、より大きな値を格納できます)。そのため、1つのオプションまたは他のオプションが爆発する大きな値/負の値を使用しない限り、「パフォーマンス」の違いが生じるとは思いません。
これは、実際にはパフォーマンスではなく、ループカウンターの要件とは関係ありません。
おそらく、完了すべき多くの反復がありました
Console.WriteLine(Int32.MaxValue); // Max interation 2147483647
Console.WriteLine(UInt32.MaxValue); // Max interation 4294967295
無署名のintには理由があるかもしれません。