我在.Net Native
和structs
中發現了(可能會發生)過度優化的問題。我不確定編譯器是否過於激進,或者我太盲目,看不到我做錯了什麼。這是.Net本地編譯和優化中的一個可能的錯誤嗎?
要重現此,請按照下列步驟操作:
步驟1:創建一個新的空白通用(win10)在的Visual Studio 2015年更新2應用定位打造10586帶有分鐘構建10240調用的項目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:運行應用程序中的Debug
x64
。你應該可以看到這個標籤:
42.5,42.5 => 107.5,107.5
步驟5:在Release
x64
運行應用程序。你應該可以看到這個標籤:
-7.5,-7.5 => 7.5,7.5
步驟6:在MainPage.xaml.cs
取消註釋line 45
和重複步驟5.現在你看到原來的標籤
42.5,42.5 => 107.5,107.5
通過註釋掉line 45
,代碼將使用Rectangle2D.Inflate2(...)
,它與Rectangle2D.Inflate1(...)
完全相同,只是它在將計算髮送到Rectangle2D
的構造函數之前創建了計算的本地副本。在調試模式下,這兩個功能完全相同。然而,在發行版中,某些內容正在得到優化。
這是我們的應用程序中的一個討厭的錯誤。你在這裏看到的代碼是從一個更大的圖書館中剝離出來的,恐怕還會有更多。在我向微軟報告這件事之前,如果你能看一看並且讓我知道爲什麼Inflate1
在發佈模式下不起作用,我將不勝感激。爲什麼我們必須創建本地副本?
結構的一大優點是使用它們的代碼總是可以進行大量優化。最大的問題在於它們歷來是優化器錯誤的頭號來源。只需發送報告。 –
感謝@HansPassant,我剛剛給微軟發送了一封電子郵件。 – Laith
有趣的是,如果將'30行'上的'foreach'循環更改爲'for'循環,它也會修復問題。奇怪的。 – Laith