Jaa


Performance-Optimized Code

こんにちは。K里です。

 

今回はパフォーマンスの最適化が実施されたコード (Performance-Optimized Code) についてお話ししたいと思います。Windows でソフトウェア開発や運用・保守を行われている方は、デバッグやダンプ解析から OS の内部関数について逆アセンブラを用いてコードを見られる場合があると思います。今回の内容はそのような方々を対象としています。

 

デバッガ (Windbg) で逆アセンブルする場合 u コマンドを使用しますが、u コマンドで確認可能なアドレスと、スタックバックトレースを表示する k コマンドで確認可能な "関数名+オフセット" とアドレスが一致しない場合があります。以下のコールスタックをご覧ください。

 

0: kd> k

ChildEBP RetAddr

f7a18d34 eeb76f66 tcpip!IPTransmit

f7a18dcc eeb8cc3c tcpip!IGMPTransmit+0x17c

f7a18e00 eeb8cc99 tcpip!SendOldReport+0x76

f7a18e18 eeb71cda tcpip!SendOldReports+0x1a

f7a18e60 eeb71a80 tcpip!IGMPTimer+0x226

f7a18e74 f790d3ff tcpip!IPTimeout+0x1b

f7a18e84 8050420f TDI!CTEpTimerHandler+0xf

f7a18fa0 8050432b nt!KiTimerListExpire+0x14b

f7a18fcc 80547eef nt!KiTimerExpiration+0xb1

f7a18ff4 80547a5b nt!KiRetireDpcList+0x61

f7a18ff8 ee296bf8 nt!KiDispatchInterrupt+0x2b

 

上記の RetAddr (戻り値のアドレス) eeb76f66 は、tcpip!IGMPTransmit+0x17c を示すことになります。しかしながら、? コマンドで “tcpip!IGMPTransmit+0x17c” を示すアドレスは eeb7703b となり、アドレス eeb76f66 と一致しません。また、アドレス eeb7703b を u コマンドで逆アセンブルすると、関数 tcpip!OpenRCE+0xb5 が表示され、一見デバッガでなんらか表示上の問題が発生しているように見えます。この要因は、コード実行時のパフォーマンスの最適化に起因して発生するものです。

 

0: kd> ?tcpip!IGMPTransmit+0x17c

Evaluate expression: -289968069 = eeb7703b

 

0: kd> u eeb7703b L1

tcpip!OpenRCE+0xb5:

eeb7703b e9b7a30100 jmp tcpip!OpenRCE+0xb1 (eeb913f7)

 

パフォーマンスの最適化が実施されていないモジュール、あるいはコード部位では、デバッガは、純粋に関数のエントリポイントアドレスと、そこからのバイトオフセットを用いて "関数名+オフセット" を表示します。

 

0: kd> u tcpip!IPTransmit

tcpip!IPTransmit:

eeb73c3e 8bff mov edi,edi // tcpip!IPTransmit

eeb73c40 55 push ebp // tcpip!IPTransmit+0x2

eeb73c41 8bec mov ebp,esp // tcpip!IPTransmit+0x3

eeb73c43 81ec10010000 sub esp,110h // tcpip!IPTransmit+0x5

  :

 (略)

 

対して、パフォーマンスの最適化が実施されたモジュール、あるいはコード部位に該当する部分では、デバッガはシンボルファイル内の情報を基に、最適化が実施される前の元のコード配列に可能な限り近付けた上で、関数のエントリポイントからのオフセット値の算出を試み、スタックバックトレースにおける "関数名+オフセット" 形式での表示を行います。そのため、tcpip!IGMPTransmit+0x17c のアドレスを逆アセンブルすると以下のように指定された関数とは異なる別の関数が表示されることになります。

 

0: kd> ?tcpip!IGMPTransmit

Evaluate expression: -289968449 = eeb76ebf

 

0: kd> ?tcpip!IGMPTransmit+0x17c

Evaluate expression: -289968069 = eeb7703b (=eeb76ebf + 0x17c)

 

0: kd> u tcpip!IGMPTransmit+0x17c

tcpip!OpenRCE+0xb5: <<<<< NOT tcpip!IGMPTransmit+0x17c

eeb7703b e9b7a30100 jmp tcpip!OpenRCE+0xb1 (eeb913f7)

eeb77040 85ff test edi,edi

eeb77042 0f85fcbfffff jne tcpip!LookupRTE+0xae (eeb73044)

eeb77048 e9ab1a0200 jmp tcpip!LookupRTE+0xb2 (eeb98af8)

eeb7704d 8b4d88 mov ecx,dword ptr [ebp-78h]

eeb77050 8d7814 lea edi,[eax+14h]

eeb77053 8bc1 mov eax,ecx

eeb77055 c1e902 shr ecx,2

 

上記のような現象が発生する要因としては、コード実行時のパフォーマンスの最適化により、関数内部のコード単位での再配置が行われた結果、指定されたアドレス (eeb76ebf) には既に他の関数内のコードが配置されているためです。

 

例えば、以下のコード例のような Function() を実行する場合、if/else 文の条件分岐にて、実際に実行される処理は、"処理 A"、もしくは "処理 B" のどちらかが実行されることになります。パフォーマンスの最適化は、このような分岐処理において、どちらの処理の実行頻度が高いかを統計的に測り、実行頻度の高いコードをより連続したメモリ領域に割り当てます。その結果、メモリ上にロードされるページ数が削減されたり、キャッシュのヒット率が向上することで、実行時のパフォーマンスの向上が図られます。このため、コンパイルの段階では、関数単位で特定の連続したメモリ領域を占めるようにアドレスが割り当てられますが、関数の処理内容に依存して、前述の最適化によりコード単位でメモリが再配置され、メモリ領域が分散する場合があります。

 

- コード例

 

  Function()

  {

    if (...)

    {

      //

      // 処理 A (実行頻度 低)

      //

    }

    else

    {

      //

      // 処理 B (実行頻度 高)

      //

    }

  }

 

アドレス 1000 ~

  Function()

  {

    if (...)

    {

    }

    else

    {

      //

      // 処理 B

      //

    }

  }

 

アドレス 2000 ~

      //

      // 処理 A

      //

 

上記のようにパフォーマンスの最適化が行われたモジュールでは、各関数内の実行コードに依存して、メモリの再配置が行われる結果、u コマンドによる逆アセンブル結果、ならびに ? コマンドによるアドレス演算結果が、k コマンドによる戻り値のアドレスと必ずしも一致しないことにご注意ください。なお、デバッガコマンドの !lmi コマンドを使用することで、対象モジュールにてパフォーマンスの最適化が実施されているかを確認することが可能です。下記に示すように Characteristics の項に "perf" が記載されていれば、そのモジュールでは最適化が実施されていることになります。

 

0: kd> !lmi tcpip

Loaded Module Info: [tcpip]

         Module: tcpip

   Base Address: eeb71000

     Image Name: tcpip.sys

   Machine Type: 332 (I386)

     Time Stamp: 485b99ad Fri Jun 20 20:51:09 2008

           Size: 58480

       CheckSum: 5ed6b

Characteristics: 10e perf

Debug Data Dirs: Type Size VA Pointer

             CODEVIEW 22, 3f3b8, 3f3b8 RSDS - GUID: {5DAF4A45-ECAA-4DE9-B4CA-8998CEB47244}

               Age: 2, Pdb: tcpip.pdb

                CLSID 4, 3f3b4, 3f3b4 [Data not mapped]

     Image Type: MEMORY - Image read successfully from loaded memory.

    Symbol Type: PDB - Symbols loaded successfully from symbol server.

                 d:\srvsymbolpub\tcpip.pdb\5DAF4A45ECAA4DE9B4CA8998CEB472442\tcpip.pdb

    Load Report: public symbols , not source indexed

                 d:\srvsymbolpub\tcpip.pdb\5DAF4A45ECAA4DE9B4CA8998CEB472442\tcpip.pdb

 

ではまた。

 

Appendix

Title: Debugging Performance-Optimized Code

URL: https://msdn.microsoft.com/en-us/library/ff541382(VS.85).aspx