20

この回答に対するコメント(パフォーマンスのために、整数の乗算/除算よりもビット シフト演算子を使用することを提案しています) で、これが実際に高速になるかどうかを質問しました。私の頭の片隅には、あるレベルでは、何かがそれを解決するのに十分賢く、同じ操作であるという考えが>> 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# コンパイラは巧妙でなくてもdivor命令を発行しています。shrJITter が生成する実際の 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 を組み合わせることで、謙虚なアプリケーション プログラマとして試すことができるどんな小さなトリックよりもはるかに賢いものになるという私の意見がさらに重要視されています :)

4

3 に答える 3

8

デバッガーを構成するまで、意味のある結果は得られません。[ツール] + [オプション]、[デバッグ]、[全般] で、[モジュールの読み込み時に JIT 最適化を抑制する] をオフにします。リリース モード構成に切り替えます。サンプル スニペット:

static void Main(string[] args) {
  int value = 4;
  int result = divideby2(value);
}

逆アセンブリが次のようになっている場合は、正しく行っています。

00000000  ret  

式を強制的に評価するには、JIT オプティマイザーを欺く必要があります。Console.WriteLine(variable) を使用すると役立ちます。次に、次のようなものが表示されるはずです。

0000000a  mov         edx,2 
0000000f  mov         eax,dword ptr [ecx] 
00000011  call        dword ptr [eax+000000BCh] 

はい、コンパイル時に結果を評価しました。かなりうまく機能しますよね。

于 2009-12-22T12:01:44.143 に答える
3

はい。Visual Studio には、これを行うための逆アセンブラーが組み込まれています。ただし、コマンドをメニュー バーに追加する必要があります。Extras/Customize/Commands に移動し (英語版で実際にそのように呼び出されているかどうかはわかりません)、メニュー バーのどこかに Debugging の下にあるコマンド Dissassembly を追加します。

次に、プログラムにブレークポイントを設定し、ブレークしたときに、この逆アセンブリ コマンドをクリックします。VS は逆アセンブルされたマシン コードを表示します。

Divider メソッドの出力例:

public static int Divider(int intArg)
    {
00000000  push        ebp  
00000001  mov         ebp,esp 
00000003  push        edi  
00000004  push        esi  
00000005  push        ebx  
00000006  sub         esp,34h 
00000009  mov         esi,ecx 
0000000b  lea         edi,[ebp-38h] 
0000000e  mov         ecx,0Bh 
00000013  xor         eax,eax 
00000015  rep stos    dword ptr es:[edi] 
00000017  mov         ecx,esi 
00000019  xor         eax,eax 
0000001b  mov         dword ptr [ebp-1Ch],eax 
0000001e  mov         dword ptr [ebp-3Ch],ecx 
00000021  cmp         dword ptr ds:[00469240h],0 
00000028  je          0000002F 
0000002a  call        6BA09D91 
0000002f  xor         edx,edx 
00000031  mov         dword ptr [ebp-40h],edx 
00000034  nop              
    return intArg / 2;
00000035  mov         eax,dword ptr [ebp-3Ch] 
00000038  sar         eax,1 
0000003a  jns         0000003F 
0000003c  adc         eax,0 
0000003f  mov         dword ptr [ebp-40h],eax 
00000042  nop              
00000043  jmp         00000045 
    }
于 2009-12-22T11:16:54.670 に答える
2

デバッグ中 (デバッグ中のみ) は、[デバッグ] - [Windows] - [逆アセンブリ] をクリックするか、対応するショートカット Ctrl+Alt+D を押します。

于 2009-12-22T11:55:43.667 に答える