1つの実行時最適化エラーについて

当初、この投稿は64ビットxlcコンパイラのエラーに専念する予定でしたが、これは何時間もキャッチできず、IBM AIXアーキテクチャサーバーで発生します。 しかし、同様のエラーが多くのコンパイラに影響を及ぼしたため、SP1をインストールしたVisual Studio 2010も例外ではありませんでした。 結局のところ、Microsoftの専門家がIBM開発者と協力して最適化コンパイラを作成していることを示唆しているため、これはおもしろそうです。



背景のビット。 長い間C ++で記述された科学プロジェクトが1つあり、現在ではメインフレームHP-UX、IBM AIX、Oracle Solarisなどの多くのプラットフォームに正常に移植されています。 全般的に、転送は、コンパイル時エラーが修正され、テストのグループが起動され、すべてのテストに合格した場合、コードが機能していると結論付けられるという事実から成ります。



数学的手順の実行速度は非常に重要であるため、-O2速度最適化キーをオンにしてコンパイルが行われます。 しかし、IBM AIXアーキテクチャーでは、何らかの理由でxlcコンパイラーがテストスイートを満たす実行可能なコードを作成できません。 同時に、-O2スイッチなしでは、すべてが正常に機能します。



もちろん、十分な時間があれば、IBM AIXメインフレームでこのエラーを直接キャッチしようとしましたが、デバッガーがないため(デバッグモードでエラーがなかったため)、コードのセクションにprintfを挿入することで、昔ながらの方法でキャッチする必要がありました。 IBM AIXへのリモートアクセスは一度も与えられませんでした。データセンターで直接作業する必要があり、端末で過ごした数時間は、エラーが発生し、非常に安定していることを除いて、わかりやすいものを理解できませんでした その結果、エラーは長い間コードに残りました。



これは、コードをVisual Studio 2010 SP1に移植しようとするまで続きました。



そして見よ! エラーは同じ元の形式で明らかになりました。つまり、32ビットモードでは、-O2フラグをオンにした場合となしですべてが正常に動作し、x64で-O2フラグをオンにすると、 IBM AIXで! これは勝利です。時間枠にとらわれずに、耕作されていないコードフィールドを慎重に掘り下げ、printfの結果をテストして、正しいテストと間違ったテストを一貫して比較できるからです。



結果はすぐに来ました。 完全なコードからの抜粋を以下に示します。これは、コードのサイズが最も小さくなっています。 パラメーターNが4であるため、このコードは32ビットモードでも機能しません。#define N 8を設定すると、32ビットで機能するがx64では機能しない元のコードが取得されます。 単純にするため(すべての人がx64を持っているわけではありませんが、多くの人が試してみたいと思うでしょう)、どのアーキテクチャでも動作しないソースコードを引用しています。



したがって、-O2スイッチを使用して、それなしでこのコードをコンパイルしてみましょう。



#include <stdio.h> #define N 4 unsigned char a[N]; void f(unsigned int k) { int i; for(i=0;i<N;++i) { a[i]=k&0xf; k>>=4; } } int main(void) { int i; static unsigned int x=0x76543210; f(x); if (a[3]==2) { printf("Error!\n"); } for(i=0;i<N;i++) { printf("%02x ", a[i]); } printf("\nsizeof(void*)=%d\n", sizeof(void*)); return 0; }
      
      





test32.cファイルにプログラムコードを記述します



コンパイルには、Visual Studio 2010 SP1を使用し、32ビットオペレーティングシステム用のコードを作成します。 次のバッチファイルを使用してビルドおよび実行します。



 call "C:\Program Files\Microsoft Visual Studio 10.0\VC\vcvarsall.bat" cl /nologo test32.c /Fano_opt >nul echo   test32 pause echo   cl /nologo -O2 test32.c /Fawith_opt >nul test32
      
      





開始後、結果が得られます。



 Setting environment fr using Microsoft Visual Studio 2010 x86 tools.   00 01 02 03 sizeof(void*)=4 Press any key to continue . . .   Error! 00 01 02 02 sizeof(void*)=4
      
      





最適化後、00 01 02 03ではなく00 01 02 02になることがわかります。



なぜこれが起こっているのですか?



最適化を有効にして取得したwith_opt.asmのアセンブラファイルを検討してください。



最適化をオフにして取得したアセンブラファイルno_opt.asmは、すべてがそこで正常に機能するため、あまり興味深いものではありません。 ご希望の方は、作業ディレクトリで見つけることができます。



有効化された最適化:

 _TEXT SEGMENT _main PROC ; COMDAT ; Line 16 mov eax, DWORD PTR ?x@?1??main@@9@9 mov cl, al shr eax, 4 mov dl, al shr eax, 4 and al, 15 ; 0000000fH and cl, 15 ; 0000000fH and dl, 15 ; 0000000fH mov BYTE PTR _a, cl mov BYTE PTR _a+1, dl mov BYTE PTR _a+2, al mov BYTE PTR _a+3, al ; Line 17 cmp al, 2 jne SHORT $LN4@main ; Line 18 push OFFSET ??_C@_07NPIJMNAB@Error?$CB?6?$AA@ call _printf add esp, 4 $LN4@main:
      
      







f()の呼び出しが実際に発生しないことに気付くのは簡単です。コンパイラはすぐに変数xの値を計算し、配列aに入力します。 さらに、最適化中に充填が正しく行われず、配列_a + 2および_a + 3の要素にレジスタalの同じ値が充填されます。



64ビットの実行可能ファイルをコンパイルする場合も同様です。 64ビットコードを使用するには、バッチファイルの最初の行を置き換えます。



 call "C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat" amd64
      
      





同じ間違った結果が得られますが、sizeof(void *)= 8でのみ、結果のコードの64ビットコードを確認します。



 Setting environment fr using Microsoft Visual Studio 2010 x64 tools.   00 01 02 03 sizeof(void*)=8 Press any key to continue . . .   Error! 00 01 02 02 sizeof(void*)=8
      
      





組み立てられたx64コードは次のようになります。



 main PROC ; COMDAT ; Line 15 $LN21: push rbx sub rsp, 32 ; 00000020H ; Line 16 mov ecx, DWORD PTR ?x@?1??main@@9@9 movzx eax, cl shr ecx, 4 and al, 15 mov BYTE PTR a, al movzx eax, cl shr ecx, 4 and cl, 15 and al, 15 mov BYTE PTR a+1, al mov BYTE PTR a+2, cl mov BYTE PTR a+3, cl ; Line 17 cmp cl, 2 jne SHORT $LN4@main ; Line 18 lea rcx, OFFSET FLAT:??_C@_07NPIJMNAB@Error?$CB?6?$AA@ call printf $LN4@main:
      
      





関数f()もここでは呼び出されず、コンパイラが変数xの値をすぐに計算し、配列aに入力することは簡単にわかります。 この場合、配列_a + 2および_a + 3の要素には、clレジスタからの同じ値が入力されますが、これは正しくありません。



その結果、関数f()のソースコードは次のように修正されました。



 void f(unsigned int k) { int i; for(i=0;i<N;++i) { a[i]=(k>>4*i)&0xf; } }
      
      







そして、IBM AIXのVisual Studio x86 / x64とxlcの両方ですべてが正常に機能しました。



-O2スイッチを使用したテストの実行速度は、最終的に約2.5〜3倍に増加しました。



UPD:誤解を排除するために、コード内の符号タイプintを符号なしintに変更しましたが、エラーは残りました。 前のオプションはここで見ることができます



UPD2: Microsoftから公式の応答を受け取りました:

Microsoftによる2011年2月11日11時17分



この問題を報告していただきありがとうございます。 この問題はVS2010 SP1で確認できます。 Visual Studioの次のメジャーリリースで修正される予定です。



イアン・ベアマン

VC ++コード生成および最適化チーム




All Articles