2016-06-19 10 views
14

.Net Nativestructsで、過剰最適化の問題を発見しました。私は、コンパイラがあまりにも攻撃的であるかどうか、または私が間違って行ったことを見るにはあまりにも盲目であるかどうかはわかりません。これは.Netネイティブのコンパイルと最適化のバグですか?

これを再現するには、次の手順に従います。

ステップ1:10240コールの分ビルドで10586を構築ターゲットのVisual Studio 2015のアップデート2で新しい空白ユニバーサル(win10)アプリを作成します。プロジェクトNativeBug私たちは同じ名前空間を持っています。

ステップ2:オープンMainPage.xaml

<Page x:Class="NativeBug.MainPage" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     mc:Ignorable="d"> 

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> 
     <!-- INSERT THIS LABEL --> 
     <TextBlock x:Name="_Label" HorizontalAlignment="Center" VerticalAlignment="Center" /> 
    </Grid> 
</Page> 

ステップは3このラベルを挿入します。コピー/ MainPage.xaml.csに次の貼り

using System; 
using System.Collections.Generic; 

namespace NativeBug 
{ 
    public sealed partial class MainPage 
    { 
     public MainPage() 
     { 
      InitializeComponent(); 

      var startPoint = new Point2D(50, 50); 
      var points = new[] 
      { 
       new Point2D(100, 100), 
       new Point2D(100, 50), 
       new Point2D(50, 100), 
      }; 

      var bounds = ComputeBounds(startPoint, points, 15); 

      _Label.Text = $"{bounds.MinX} , {bounds.MinY} => {bounds.MaxX} , {bounds.MaxY}"; 
     } 

     private static Rectangle2D ComputeBounds(Point2D startPoint, IEnumerable<Point2D> points, double strokeThickness = 0) 
     { 
      var lastPoint = startPoint; 
      var cumulativeBounds = new Rectangle2D(); 

      foreach (var point in points) 
      { 
       var bounds = ComputeBounds(lastPoint, point, strokeThickness); 
       cumulativeBounds = cumulativeBounds.Union(bounds); 
       lastPoint = point; 
      } 

      return cumulativeBounds; 
     } 

     private static Rectangle2D ComputeBounds(Point2D fromPoint, Point2D toPoint, double strokeThickness) 
     { 
      var bounds = new Rectangle2D(fromPoint.X, fromPoint.Y, toPoint.X, toPoint.Y); 

      // ** Uncomment the line below to see the difference ** 
      //return strokeThickness <= 0 ? bounds : bounds.Inflate2(strokeThickness); 

      return strokeThickness <= 0 ? bounds : bounds.Inflate1(strokeThickness); 
     } 
    } 

    public struct Point2D 
    { 
     public readonly double X; 
     public readonly double Y; 

     public Point2D(double x, double y) 
     { 
      X = x; 
      Y = y; 
     } 
    } 

    public struct Rectangle2D 
    { 
     public readonly double MinX; 
     public readonly double MinY; 
     public readonly double MaxX; 
     public readonly double MaxY; 

     private bool IsEmpty => MinX == 0 && MinY == 0 && MaxX == 0 && MaxY == 0; 

     public Rectangle2D(double x1, double y1, double x2, double y2) 
     { 
      MinX = Math.Min(x1, x2); 
      MinY = Math.Min(y1, y2); 
      MaxX = Math.Max(x1, x2); 
      MaxY = Math.Max(y1, y2); 
     } 

     public Rectangle2D Union(Rectangle2D rectangle) 
     { 
      if (IsEmpty) 
      { 
       return rectangle; 
      } 

      var newMinX = Math.Min(MinX, rectangle.MinX); 
      var newMinY = Math.Min(MinY, rectangle.MinY); 
      var newMaxX = Math.Max(MaxX, rectangle.MaxX); 
      var newMaxY = Math.Max(MaxY, rectangle.MaxY); 

      return new Rectangle2D(newMinX, newMinY, newMaxX, newMaxY); 
     } 

     public Rectangle2D Inflate1(double value) 
     { 
      var halfValue = value * .5; 

      return new Rectangle2D(MinX - halfValue, MinY - halfValue, MaxX + halfValue, MaxY + halfValue); 
     } 

     public Rectangle2D Inflate2(double value) 
     { 
      var halfValue = value * .5; 
      var x1 = MinX - halfValue; 
      var y1 = MinY - halfValue; 
      var x2 = MaxX + halfValue; 
      var y2 = MaxY + halfValue; 

      return new Rectangle2D(x1, y1, x2, y2); 
     } 
    } 
} 

ステップ4:でアプリケーションを実行しますDebugx64。あなたはこのラベルが表示されます。

42.5、42.5 => 107.5、107.5

ステップ5Releasex64でアプリケーションを実行します。今、あなたは、元のラベル

を参照してください MainPage.xaml.csでのコメントを外し line 45を繰り返し、ステップ5:

-7.5、-7.5 => 7.5、7.5

ステップ6:あなたはこのラベルが表示されます

42.5、42.5 => 107.5、107.5


0123それは Rectangle2Dのコンストラクタにそれらを送信する前に、計算のローカルコピーを作成する以外

line 45をコメントアウトすることで、コードは正確にRectangle2D.Inflate1(...)と同じであるRectangle2D.Inflate2(...)を使用します。デバッグモードでは、これら2つの機能はまったく同じです。リリースでは、何かが最適化されています。

これは、アプリの厄介なバグでした。ここに表示されているコードは、はるかに大きなライブラリからは削除されましたが、もっと多いかもしれません。私がこれをMicrosoftに報告する前に、あなたが見て、Inflate1がリリースモードで動作しない理由を教えてもらえれば幸いです。なぜローカルコピーを作成する必要がありますか?

+5

構造体の大きな利点は、それらを使用するコードは、常に重く最適化することができるということです。その大きな問題は、歴史的にオプティマイザのバグの第1位の原因であることです。ただ報告書を送ってください。 –

+0

ありがとう@HansPassant、私はMicrosoftに電子メールを送ったばかりです。 – Laith

+1

面白いことに、 'line 30 'の' foreach'ループを 'for'ループに変更すると、問題も同様に修正されます。奇妙な。 – Laith

答えて

4

なぜこの質問には恩恵があるのか​​わかりません。はい、@Mattがあなたに言ったようにバグです。彼は知っている、彼は.NETネイティブで動作します。また、一時的な回避策を文書化し、属性を使用してメソッドがオプティマイザによってインライン化されないようにしました。オプティマイザのバグを回避するためによく働くトリックです。彼らはそれが固定取得します

using System.Runtime.CompilerServices; 
.... 
    [MethodImpl(MethodImplOptions.NoInlining)] 
    public Rectangle2D Inflate1(double value) 
    { 
     // etc... 
    } 

、次のメジャーリリースでは、通常の約束です。

関連する問題