2016-06-19 60 views
14

Ho scoperto un problema con (cosa potrebbe essere) l'ottimizzazione eccessiva in .Net Native e structs. Non sono sicuro che il compilatore sia troppo aggressivo, o sono troppo cieco per vedere cosa ho fatto di sbagliato.Si tratta di un possibile errore nella compilazione e ottimizzazione Native .Net?

Per riprodurre questo, attenersi alla seguente procedura:

Fase 1: Creare un nuovo Universale (win10) app bianco in Visual Studio 2015 Update 2 mira costruire 10586 con un minimo accumulo di 10240: Chiamare il progetto NativeBug quindi abbiamo lo stesso spazio dei nomi.

Fase 2: Aprire MainPage.xaml e inserire questa etichetta

<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> 

Fase 3: Copia/incolla il seguente in 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); 
     } 
    } 
} 

Fase 4: Eseguire l'applicazione in Debugx64. Si dovrebbe vedere questa etichetta:

42,5, 42,5 => 107,5, 107,5

Fase 5: Eseguire l'applicazione in Releasex64. Si dovrebbe vedere questa etichetta:

-7,5, -7,5 => 7.5, 7,5

Passo 6: Decommentare line 45 in MainPage.xaml.cs e ripetere il punto 5. Ora si vede l'etichetta originale

42,5, 42,5 => 107,5, 107,5


Con il commento line 45, il codice utilizzerà Rectangle2D.Inflate2(...) che è esattamente lo stesso di Rectangle2D.Inflate1(...) tranne che crea una copia locale dei calcoli prima di inviarli al costruttore di Rectangle2D. In modalità debug, queste due funzioni sono esattamente le stesse. Nel rilascio, tuttavia, qualcosa viene ottimizzato.

Questo era un brutto bug nella nostra app. Il codice che vedi qui è stato rimosso da una biblioteca molto più grande e temo che ci potrebbe essere di più. Prima di segnalarlo a Microsoft, sarei grato se potessi dare un'occhiata e fammi sapere perché Inflate1 non funziona in modalità di rilascio. Perché dobbiamo creare copie locali?

+5

Il grande vantaggio di una struttura è che il codice che li utilizza può sempre essere fortemente ottimizzato. Il grosso problema è che sono storicamente la fonte numero 1 di bug ottimizzatori. Basta inviare il rapporto. –

+0

Grazie a @HansPassant, ho appena inviato a Microsoft un'e-mail. – Laith

+1

Una cosa divertente, se cambi il ciclo 'foreach' su' line 30' in un ciclo 'for', risolve anche il problema. Strano. – Laith

risposta

4

Non mi è chiaro perché questa domanda ha una taglia. Sì, è un bug come ti ha detto @Matt. Sa, lavora su .NET Native. E ha documentato la soluzione temporanea, utilizzare un attributo per evitare che il metodo venga sottolineato dall'ottimizzatore. Un trucco che spesso funziona per aggirare i bug di ottimizzazione.

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

Faranno farlo fisso, prossima major release è la solita promessa.