none
最適化を行うと計算結果が変わる

    質問

  • Visual Studio の C++の最適化に関する質問です。
    文末のソースコードを以下の再現方法でビルドした結果が期待したものと異なります。
    最適化オプションを変更したことにより発生したため、これが原因だと思われるのですが、何か使用方法に誤り等あるでしょうか?
    それともコンパイラ等の不具合なのでしょうか?
    ご存知のかたがおられましたらご教示お願いします。

    ■再現方法
    ファイル>新規プロジェクト>Visual C++>Windows コンソールアプリケーション
    を選択し、任意のプロジェクト名を設定。
    生成されたコードに対して、文末のソースコードを置換。

    構成をReleaseに変更し、
    プロジェクトのプロパティの
    C/C++>最適化>速度またはサイズを優先に「実行速度を優先(/Ot)」を選択。
    ビルドし、デバッグなしで実行。

    ■ソースコードの説明
    関数funcに4つのアドレスを入力すると、先頭3つに結果が格納されるアルゴリズム。
    4つ目の配列の内容に従って結果が変わる実装で、添付の内容のように全て0で入力した場合、for文内のif文の条件式は一度もtrueにならないため、結果として以下の数値が格納される(はず)。
    *a = 0;
    *b = 0;
    *c = 123456;
    funcを抜けた後はcoutにcの値を出力。

    ■結果
    期待する結果:123456が出力される。
    実際の結果:1000が出力される。

    ■検討した内容
    一度も通過しないはずのif文の中をコメントアウトするなど、ロジックをこれ以上シンプルな方向に変更すると再現しなくなります。

    ■開発環境
    Microsoft Visual Studio Enterprise 2017(2)
    Version 15.8.2

    Microsoft .NET Framework
    Version 4.7.02046

    ---- 以下、ソースコード ----------------
    #include "pch.h"
    #include <iostream>
    int func(int *a, int *b, int *c, int *temp);

    int main()
    {
    int a;
    int b;
    int c;

    int temp[12] = { 0,0,0,0,0,0,0,0,0,0,0,0};

    func(&a, &b, &c, temp);
    std::cout << c;
    }
    int func(int *a, int *b, int *c, int *temp)
    {
    int aa = 0;
    int bb = 0;
    int cc = 123456;

    for (int x = 0; x < 12; x++) {
    if (temp[x] < bb) {
    aa = 0;
    bb = temp[x];
    cc = 0;
    }
    }

    *a = aa;
    *b = bb;
    *c = cc;

    return(0);

    }


    2018年11月8日 2:07

すべての返信

  • すみません。ただいま手元に Visual Studio がないので、すぐに検証ができないのですが、ご質問の現象が事実でしたら、私の認識ではコンパイラの不具合だと思いました。英語ですが、Visual Studio C++ の不具合を登録する場所がございますので、登録してみてはいかがでしょうか?(既知の不具合かどうかは確認していません)

    https://developercommunity.visualstudio.com/spaces/62/index.html

    2018年11月8日 2:50
  • 手元の環境は 15.8.9(現行最新)で試してみました。

    OSは、Win10 x64 1809(リリース直後バージョン+その後のWindowsUpdate適用版)

    手順は

    1. 新規に C++ コンソールを作成
    2. 添付ソースを貼り付け(pch.hの内容は変更せず)
    3. Win32 Debug で実行
    4. 問題がないことを確認
    5. Win32 Release で実行
    6. 再現

    デバッグ実行しても発生します。デバッガで内容を見ると、temp の中身がぐちゃぐちゃでした。

    最適化のバグのようです。


    面白くないですが、面白い現象なのは func を以下のように修正しても再現します。
    でも、ループ内(コメントアウトしてる部分で表示)だと正しく評価される。何が悪いのかさっぱりわかりませんね。。。

    int func( int *a, int *b, int *c, int *temp )
    {
    	int aa = 0;
    	int bb = 0;
    	int cc = 123456;
    	for( int x = 0; x < 12; x++ )
    	{
    		std::cout << x << ":" << temp[x] << std::endl;
    	}
    	for( int x = 0; x < 12; x++ )
    	{
    		//std::cout << x << ":" << temp[x] << std::endl;
    		if( temp[x] < bb )
    		{
    			aa = 0;
    			bb = temp[x];
    			cc = 0;
    		}
    	}
    
    	*a = aa;
    	*b = bb;
    	*c = cc;
    
    	return(0);
    
    }


    とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx


    2018年11月8日 4:12
  • kenjinote様、とっちゃん様

    さっそくの回答ありがとうございます。コンパイラの不具合である可能性が高いですか。。。自分のミスであって欲しかった。。。紹介していただいたURLで既出確認したのち、問い合わせしたいと思います。

    2018年11月8日 8:24
  • アセンブラで見ると、ループが11回までの場合はfuncの呼び出しは行われずに変数cに直接定数123456が設定されるけれど、12回以上にするとfuncが呼び出されてループの中で引数ccが割り当てられているレジスタにどこから出てきたのか定数1000が設定されている不思議な壊れ方ですね。

    funcの中身みると訳の分からないSIMD命令が大量に使われ逆にステップ数が増えてます。
    コード生成で拡張命令セットの使用を無効にしてやると問題が改善されて、funcを呼ばずに定数を入れるコードになったり、呼ばれても少ないステップ数になりますね。
    WindowsSDKバージョンを10.0.16299に戻すと発生しないので、以降に入り込んだバグですかね。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2018年11月8日 10:37
    2018年11月8日 10:22
  • 下記Visual Studio 2015 の「/Os /Ot」の説明
     https://msdn.microsoft.com/ja-jp/library/f9534wye.aspx

    によりますと。

    (引用)/Os または /Ot を使用する場合、コードを最適化するために /Og も指定する必要があります。


    とのことです。

    /Ogは「グローバルの最適化」とのことです。
     https://msdn.microsoft.com/ja-jp/library/1yk3ydd7.aspx

    /Otのみを指定なさっているならば、/Ogも指定してみてはどうでしょう。

    2018年11月9日 4:59
  • 返答ありがとうございます。逆アセンブルでたしかに1000を直値で入力する記述を確認できました。またループの回数を減らすと完全に結果のみを格納することも確認できました。

    Microsoftに問い合わせると同時に、暫定対策としてはここでは最適化を行わないように実装を組み替えるようにします。添付のソースでは簡略化しましたが、配列の中身の最小値を算出してその値によって処理を分ける云々。。。というもう少し複雑なロジックがあったのですが、最適化を外したほうが処理が速そうなことが確認できました。


    2018年11月9日 7:18
  • アドバイスありがとうございます。

    ご指定の最適化のオプションを変更してみましたが、残念ながら改善はされませんでした。

    とはいえこの注意事項は見過ごしていました。最適化回りのオプションは無邪気に切り替えてはいけませんね。勉強になりました。

    2018年11月9日 7:24
  • int func(int *a, int *b, int *c, volatile int *temp);
    関係各所を同じように。一応、123456になります。
    なぜかはわからない。

    Jitta@わんくま同盟

    2018年11月9日 7:56
  • Jitta様

    返答遅れて申し訳ありません。

    逆アセンブリを見る限り、volatileを入れると123456をそのまま返す処理にインライン展開されているようでした。むしろvolatileを入れた方が最適化されちゃっていますね。。。


    2018年11月12日 1:09
  • 本日、Visual Studio 2017 15.9.0がリリースされたため試してみましたが、改善されていませんでした。

    アセンブリコードを見ても、ほんとどこから現れたか1000が代入されますねぇ…。

    2018年11月15日 4:24