- PR -

VBコンパイラの最適化により挙動が異なる

投稿者投稿内容
KI
大ベテラン
会議室デビュー日: 2007/01/10
投稿数: 239
投稿日時: 2007-02-28 13:22
VB2005で最適化を有効にしたときと、有効にしなかったときとで
プログラムの振る舞いが異なるという問題で悩んでいます。
再現できるコードを以下に示します。

コード:

Public Class OptimizeTest

    Public Sub Execute()

        Dim testArray As String() = New String() {"a", "b", "c", "d", "e", "f"}

        Console.WriteLine("Out1")
        Out1(testArray)
        Console.WriteLine(String.Empty)

        Console.WriteLine("Out2")
        Out2(testArray)
        Console.WriteLine(String.Empty)

    End Sub

    Private Sub Out1(ByVal testArray As String())
        For i As Integer = 0 To 2
            Console.WriteLine(testArray(i * 2 + 1))
        Next
    End Sub

    Private Sub Out2(ByVal testArray As String())
        For i As Integer = 0 To 2
            If testArray(i * 2 + 1).Length = 1 Then
                Console.WriteLine(testArray(i * 2 + 1))
            End If
        Next
    End Sub

End Class

'最適化なしでコンパイルした場合の出力
'-----------------------------------------------------------
'Out1
'b
'd
'f
'
'Out2
'b
'd
'f
'
'最適化ありでコンパイルした場合の出力
'-----------------------------------------------------------
'Out1
'b
'd
'f
'
'Out2
'c
'e




Out1のコードは意図通りに出力されます。
Out2では、(このサンプルでは全ての要素が長さ1なので無駄な処理ですが)
If文で要素の文字列の長さが1かどうかをチェックしています。
これを入れることにより、最適化を有効にしたときの挙動が変わってしまうようです。

確かに配列のインデックス計算(i * 2 + 1)を2回行っていたり、
配列の同じ要素へのアクセスも2回行っていたりと、多少無駄なことはしていると思いますが、
もっと汚いコードも山のようにありますし、
このコードがさほど大きな問題を抱えているようには、私には見えません。

部分的な回避策は見つかっています。配列の要素を変数に格納したりすれば解決します。
しかしながらこういう挙動をされると、他にも不具合が出るのではないかが怖くて
最適化を無効にしてリリースしたくもなるのですが…

最適化により挙動が異なる理由がわかる方がいらっしゃいましたら教えてください。
よろしくお願いします。
よねKEN
ぬし
会議室デビュー日: 2003/08/23
投稿数: 472
投稿日時: 2007-02-28 13:46
手元に.NET Framework 2.0環境がないので、
.NET Framework 1.1 SDKで試してみました。

提示のコードだけでは実行できないので、
先頭にImports Systemを追加し、Mainメソッドとて以下のコード追加して試しました。

コード:
	Public Shared Sub Main()
		Dim op As New OptimizeTest()
		op.Execute()
	End Sub



追加したコードが影響していないとは言い切れないですが、
再現しませんでした。

状況からすると.NET Framework2.0でのVBコンパイラの最適化のバグだろうと思いますが、
何がまずいかは製作者にしかわからないと思います。
#最適化により結果が変わるということはあるにしても
#結果が真逆になるような状況は最適化しているから仕方ないとは言えないでしょうし。

原因が判明するかどうかはわかりませんが、現状分析をもう一歩進めるのであれば、
KIさんの環境で作成した提示コードの最適化あり/なしの各EXEを
ildasmツールで逆アセンブルして、差異を調べてみてはどうでしょうか。
甕星
ぬし
会議室デビュー日: 2003/03/07
投稿数: 1185
お住まい・勤務地: 湖の見える丘の上
投稿日時: 2007-02-28 14:00
引用:

KIさんの書き込み (2007-02-28 13:22) より:
しかしながらこういう挙動をされると、他にも不具合が出るのではないかが怖くて
最適化を無効にしてリリースしたくもなるのですが…


はい、私も同じ理由で最適化を外してリリースする事があるので、よく分かります。

引用:

最適化により挙動が異なる理由がわかる方がいらっしゃいましたら教えてください。
よろしくお願いします。


VS2005のすぐ試せる環境が手元にないのですが、これだけのコードで発生するなら、コンパイラのバグでしょうね。実装がこなれていないコンパイラや、高度な最適化を行うコンパイラでは、良くあることです。Microsoftにバグの報告を出した上で、当面は最適化を無効にするのが良いかと思います
KI
大ベテラン
会議室デビュー日: 2007/01/10
投稿数: 239
投稿日時: 2007-02-28 15:40
よねKENさん、甕星さん、お返事ありがとうございます。
コンパイラのバグなのか、コードが悪いのかの区別が自分でつけられなかったのですが、
やはりこれだけのコードで出るとなるとバグの可能性が高いのですね。

すみません。Mainも書いたほうがよかったですね。
上記のサンプルはコンソールアプリケーションとして作成して
スタートアップがModule1で、以下のようなコードになっています。
Mainは以下のようになっています。

コード:
Module Module1

    Sub Main()
        Dim obj As New OptimizeTest()
        obj.Execute()
    End Sub

End Module



引用:

よねKENさんの書き込み (2007-02-28 13:46) より:
KIさんの環境で作成した提示コードの最適化あり/なしの各EXEを
ildasmツールで逆アセンブルして、差異を調べてみてはどうでしょうか。


試してみました。MSILの読み方があまりよくわからないため自信はないのですが、
GetLengthのIf文の比較結果を変数に格納するかどうかという違いと、
forループを継続するかどうかの判定で「i」と比較するための値を
変数に格納するかどうかの違いくらいのもので、大きな差異はないように思えます。
一応貼り付けておきますので、何かお気づきになった方がいたら教えてください。

最適化なし(Out2メソッド部のみ)
コード:
.method private instance void  Out2(string[] testArray) cil managed
{
  // コード サイズ       49 (0x31)
  .maxstack  3
  .locals init ([0] int32 i,
           [1] bool VB$CG$t_bool$S0,
           [2] int32 VB$CG$t_i4$S0)
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  stloc.0
  IL_0003:  ldarg.1
  IL_0004:  ldloc.0
  IL_0005:  ldc.i4.2
  IL_0006:  mul.ovf
  IL_0007:  ldc.i4.1
  IL_0008:  add.ovf
  IL_0009:  ldelem.ref
  IL_000a:  callvirt   instance int32 [mscorlib]System.String::get_Length()
  IL_000f:  ldc.i4.1
  IL_0010:  ceq
  IL_0012:  stloc.1
  IL_0013:  ldloc.1
  IL_0014:  brfalse.s  IL_0023
  IL_0016:  ldarg.1
  IL_0017:  ldloc.0
  IL_0018:  ldc.i4.2
  IL_0019:  mul.ovf
  IL_001a:  ldc.i4.1
  IL_001b:  add.ovf
  IL_001c:  ldelem.ref
  IL_001d:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0022:  nop
  IL_0023:  nop
  IL_0024:  nop
  IL_0025:  ldloc.0
  IL_0026:  ldc.i4.1
  IL_0027:  add.ovf
  IL_0028:  stloc.0
  IL_0029:  ldloc.0
  IL_002a:  ldc.i4.2
  IL_002b:  stloc.2
  IL_002c:  ldloc.2
  IL_002d:  ble.s      IL_0003
  IL_002f:  nop
  IL_0030:  ret
} // end of method OptimizeTest:ut2


最適化あり(Out2メソッド部のみ)
コード:
.method private instance void  Out2(string[] testArray) cil managed
{
  // コード サイズ       38 (0x26)
  .maxstack  3
  .locals init (int32 V_0)
  IL_0000:  ldc.i4.0
  IL_0001:  stloc.0
  IL_0002:  ldarg.1
  IL_0003:  ldloc.0
  IL_0004:  ldc.i4.2
  IL_0005:  mul.ovf
  IL_0006:  ldc.i4.1
  IL_0007:  add.ovf
  IL_0008:  ldelem.ref
  IL_0009:  callvirt   instance int32 [mscorlib]System.String::get_Length()
  IL_000e:  ldc.i4.1
  IL_000f:  bne.un.s   IL_001d
  IL_0011:  ldarg.1
  IL_0012:  ldloc.0
  IL_0013:  ldc.i4.2
  IL_0014:  mul.ovf
  IL_0015:  ldc.i4.1
  IL_0016:  add.ovf
  IL_0017:  ldelem.ref
  IL_0018:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_001d:  ldloc.0
  IL_001e:  ldc.i4.1
  IL_001f:  add.ovf
  IL_0020:  stloc.0
  IL_0021:  ldloc.0
  IL_0022:  ldc.i4.2
  IL_0023:  ble.s      IL_0002
  IL_0025:  ret
} // end of method OptimizeTest:ut2



引用:

はい、私も同じ理由で最適化を外してリリースする事があるので、よく分かります。


それを聞いて安心できました。
最適化は当然すべきものだと思っていましたので、悩んでいました。
パフォーマンスには幾分か影響するのでしょうが、
信頼性を落としてまで効率性を取ることはできませんので、
今回は最適化を外してリリースで対応したいと思います。


ありがとうございました。
rucio
ベテラン
会議室デビュー日: 2002/11/27
投稿数: 98
投稿日時: 2007-02-28 16:18
Visual Studio 2005 Team Suite SP1で再現することを確認しました。
OSはWindows XP Pro SP2です。

最適化にこんな問題があるとは知りませんでした。
私も最適化はチェックしないでおきます。
すけけん
会議室デビュー日: 2007/02/26
投稿数: 7
投稿日時: 2007-02-28 16:29
自分も知りませんでした。自分の環境でも再現したので投稿しておきます。

・VisualBasic2005 ExpressEdition
・Windows XP Pro SP2

ちなみにExpressEditionの場合はコンパイルの詳細設定ができないので
プロジェクトフォルダ\bin\Release配下のexeは最適化が有効の状態で
コンパイルされるため不具合が起きます。
なのでプロジェクトフォルダ\bin\Debug配下のexeを使用しないと
正常動作しませんでした。

有用な情報に感謝です。
よねKEN
ぬし
会議室デビュー日: 2003/08/23
投稿数: 472
投稿日時: 2007-02-28 16:35
引用:

最適化あり(Out2メソッド部のみ)
コード:
.method private instance void  Out2(string[] testArray) cil managed
{
  :略
} // end of method OptimizeTest:ut2





これは実際に問題の発生しているミニマムコードで作成したEXEを
ildasmで逆アセンブルしたもので、間違いないでしょうか?

疑うわけではないのですが、
最適化あり版のOut2メソッドのILコードを読みましたが、ILレベルではまったく問題ありません。
また、実際に私の方で実験したプログラムを逆アセンブルして、
提示のOut2メソッドのILコードに置き換えて、ilasmでビルドして実行しましたが、
期待どおり(b,d,fが出力)の結果でした。
ただし、今回も.NET Framework1.1環境でのビルド、実行です。
うちに帰ったら2.0でも試してみたいと思います。

#私の検証ミスの可能性もありますが、ILとして問題ないとすると
#JITコンパイル時の最適化のバグの可能性もあるかもしれません・・・
KI
大ベテラン
会議室デビュー日: 2007/01/10
投稿数: 239
投稿日時: 2007-02-28 17:16
引用:

よねKENさんの書き込み (2007-02-28 16:35) より:
これは実際に問題の発生しているミニマムコードで作成したEXEを
ildasmで逆アセンブルしたもので、間違いないでしょうか?



ご指摘頂いたので再度出力してみましたが、やはり全く同じものが取れました。
再度出力したものと、私の投稿記事からコピーしたものと比較ツールで比較してみて
(絵文字に化けた部分を除いて)相違が見当たりませんでしたので間違いないかと思います。

逆アセンブリは今日初めて使ったので、手順が違っていたらまずいので念のため書いておきます。

1) VisualStudioでRelease構成でビルドする。
2) MSIL 逆アセンブラ(GUI)を起動
3) ファイル→開くで、1)で生成されたexe(Releaseフォルダ内にあるもの)を開く
4) ツリーを展開してOut2メソッドをダブルクリック
5) 表示されたウィンドウ内のテキストをコピー

以上です。

スキルアップ/キャリアアップ(JOB@IT)