この回答に対するコメント(パフォーマンスのために、整数の乗算/除算よりもビット シフト演算子を使用することを提案しています) で、これが実際に高速になるかどうかを質問しました。私の頭の片隅には、あるレベルでは、何かがそれを解決するのに十分賢く、同じ操作であるという考えが>> 1
あります. / 2
しかし、これが本当なのか、もし本当ならどのレベルで起こるのか、今疑問に思っています。
optimize
テスト プログラムは、引数をそれぞれ除算およびシフトする 2 つのメソッドに対して、次の比較 CIL ( on を使用) を生成します。
IL_0000: ldarg.0
IL_0001: ldc.i4.2
IL_0002: div
IL_0003: ret
} // end of method Program::Divider
対
IL_0000: ldarg.0
IL_0001: ldc.i4.1
IL_0002: shr
IL_0003: ret
} // end of method Program::Shifter
そのため、C# コンパイラは巧妙でなくてもdiv
or命令を発行しています。shr
JITter が生成する実際の x86 アセンブラを見たいのですが、これを行う方法がわかりません。それは可能ですか?
編集して追加
所見
そのデバッガーオプションに関する重要な情報が含まれていたため、nobugzからのものを受け入れました。最終的に私のために働いたのは:
- リリース構成に切り替える
- で
Tools | Options | Debugger
、「モジュールのロード時に JIT 最適化を抑制する」をオフにします (つまり、JIT 最適化を許可します) 。 - 同じ場所で、'Enable Just My Code' をオフにします (つまり、すべてのコードをデバッグします) 。
Debugger.Break()
ステートメントをどこかに置く- アセンブリを構築する
- .exe を実行し、壊れたら既存の VS インスタンスを使用してデバッグする
- 逆アセンブリ ウィンドウに、実行される実際の x86 が表示されます。
結果は控えめに言っても啓発的でした - JITter は実際に算術演算を実行できることがわかりました! [逆アセンブル] ウィンドウから編集したサンプルを次に示します。を使用して 2 のべき乗で除算するさまざまな-Shifter
方法があり>>
ます。を使用して整数で除算するさまざまな-Divider
方法/
Console.WriteLine(string.Format("
{0}
shift-divided by 2: {1}
divide-divided by 2: {2}",
60, TwoShifter(60), TwoDivider(60)));
00000026 mov dword ptr [edx+4],3Ch
...
0000003b mov dword ptr [edx+4],1Eh
...
00000057 mov dword ptr [esi+4],1Eh
静的に 2 で割る両方のメソッドはインライン化されているだけでなく、実際の計算は JITter によって行われています。
Console.WriteLine(string.Format("
{0}
divide-divided by 3: {1}",
60, ThreeDivider(60)));
00000085 mov dword ptr [esi+4],3Ch
...
000000a0 mov dword ptr [esi+4],14h
statically-divide-by-3 と同じです。
Console.WriteLine(string.Format("
{0}
shift-divided by 4: {1}
divide-divided by 4 {2}",
60, FourShifter(60), FourDivider(60)));
000000ce mov dword ptr [esi+4],3Ch
...
000000e3 mov dword ptr [edx+4],0Fh
...
000000ff mov dword ptr [esi+4],0Fh
そして静的に4で割る。
最高の:
Console.WriteLine(string.Format("
{0}
n-divided by 2: {1}
n-divided by 3: {2}
n-divided by 4: {3}",
60, Divider(60, 2), Divider(60, 3), Divider(60, 4)));
0000013e mov dword ptr [esi+4],3Ch
...
0000015b mov dword ptr [esi+4],1Eh
...
0000017b mov dword ptr [esi+4],14h
...
0000019b mov dword ptr [edi+4],0Fh
インライン化され、これらすべての静的除算が計算されます!
しかし、結果が静的でない場合はどうなるでしょうか? コンソールから整数を読み取るコードを追加しました。これは、その分割に対して生成されるものです。
Console.WriteLine(string.Format("
{0}
shift-divided by 2: {1}
divide-divided by 2: {2}",
i, TwoShifter(i), TwoDivider(i)));
00000211 sar eax,1
...
00000230 sar eax,1
したがって、CIL が異なっていても、JITter は 2 で割ると右に 1 シフトすることを認識しています。
Console.WriteLine(string.Format("
{0}
divide-divided by 3: {1}", i, ThreeDivider(i)));
00000283 idiv eax,ecx
そして、3 で割るには割る必要があることを認識しています。
Console.WriteLine(string.Format("
{0}
shift-divided by 4: {1}
divide-divided by 4 {2}",
i, FourShifter(i), FourDivider(i)));
000002c5 sar eax,2
...
000002ec sar eax,2
そして、4 で割ると 2 で右シフトすることを知っています。
最後に(また最高!)
Console.WriteLine(string.Format("
{0}
n-divided by 2: {1}
n-divided by 3: {2}
n-divided by 4: {3}",
i, Divider(i, 2), Divider(i, 3), Divider(i, 4)));
00000345 sar eax,1
...
00000370 idiv eax,ecx
...
00000395 sar esi,2
メソッドをインライン化し、静的に利用可能な引数に基づいて、物事を行うための最良の方法を考え出しました。良い。
そうです、C# と x86 の間のスタックのどこかに、それを解決するのに十分なほど賢い何かが>> 1
あり、/ 2
それらは同じです。そして、これらすべてのことから、C# コンパイラ、JITter、および CLR を組み合わせることで、謙虚なアプリケーション プログラマとして試すことができるどんな小さなトリックよりもはるかに賢いものになるという私の意見がさらに重要視されています :)