这是.Net Native 编译和优化中可能存在的错误吗?
Is this a possible bug in .Net Native compilation and optimization?
我在 .Net Native
和 structs
中发现了(可能是)过度优化的问题。我不确定是编译器太激进了,还是我太盲目了,看不出我做错了什么。
要重现这一点,请按照下列步骤操作:
第 1 步:在 Visual Studio 2015 Update 2 中创建一个新的空白通用 (win10) 应用程序,目标版本为 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步: Copy/paste下面变成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
。在调试模式下,这两个功能完全一样。然而,在发布中,一些东西正在被优化。
这是我们应用程序中的一个严重错误。您在此处看到的代码是从一个更大的库中删除的,恐怕还有更多。在我向 Microsoft 报告此问题之前,如果您能看一下并告诉我为什么 Inflate1
在发布模式下不起作用,我将不胜感激。为什么我们必须创建本地副本?
我不太清楚为什么这个问题有赏金。是的,正如@Matt 告诉您的那样,这是一个错误。他知道,他从事 .NET Native 方面的工作。他记录了临时解决方法,使用属性来防止优化器内联该方法。一个经常用来绕过优化器错误的技巧。
using System.Runtime.CompilerServices;
....
[MethodImpl(MethodImplOptions.NoInlining)]
public Rectangle2D Inflate1(double value)
{
// etc...
}
他们会修复它,下一个主要版本是通常的承诺。
我在 .Net Native
和 structs
中发现了(可能是)过度优化的问题。我不确定是编译器太激进了,还是我太盲目了,看不出我做错了什么。
要重现这一点,请按照下列步骤操作:
第 1 步:在 Visual Studio 2015 Update 2 中创建一个新的空白通用 (win10) 应用程序,目标版本为 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步: Copy/paste下面变成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
。在调试模式下,这两个功能完全一样。然而,在发布中,一些东西正在被优化。
这是我们应用程序中的一个严重错误。您在此处看到的代码是从一个更大的库中删除的,恐怕还有更多。在我向 Microsoft 报告此问题之前,如果您能看一下并告诉我为什么 Inflate1
在发布模式下不起作用,我将不胜感激。为什么我们必须创建本地副本?
我不太清楚为什么这个问题有赏金。是的,正如@Matt 告诉您的那样,这是一个错误。他知道,他从事 .NET Native 方面的工作。他记录了临时解决方法,使用属性来防止优化器内联该方法。一个经常用来绕过优化器错误的技巧。
using System.Runtime.CompilerServices;
....
[MethodImpl(MethodImplOptions.NoInlining)]
public Rectangle2D Inflate1(double value)
{
// etc...
}
他们会修复它,下一个主要版本是通常的承诺。