以下代码在.NET 4.5上运行发布配置时会生成以下输出...
Without virtual: 0.333333333333333
With virtual: 0.333333343267441
(在调试中运行时,两个版本都会以0.333333343267441
为结果。)
我可以看到将一个浮点除以一个short并将其返回一个double可能会在一定点之后产生垃圾。
我的问题是:当分母中提供短片的属性是虚拟还是非虚拟时,有人可以解释为什么结果会有所不同吗?
public class ProvideThreeVirtually
{
public virtual short Three { get { return 3; } }
}
public class GetThreeVirtually
{
public double OneThird(ProvideThreeVirtually provideThree)
{
return 1.0f / provideThree.Three;
}
}
public class ProvideThree
{
public short Three { get { return 3; } }
}
public class GetThree
{
public double OneThird(ProvideThree provideThree)
{
return 1.0f / provideThree.Three;
}
}
class Program
{
static void Main()
{
var getThree = new GetThree();
var result = getThree.OneThird(new ProvideThree());
Console.WriteLine("Without virtual: {0}", result);
var getThreeVirtually = new GetThreeVirtually();
var resultV = getThreeVirtually.OneThird(new ProvideThreeVirtually());
Console.WriteLine("With virtual: {0}", resultV);
}
}
答案 0 :(得分:1)
我相信詹姆斯的推测是正确的,这是一个JIT优化。 JIT在可能的情况下执行不太精确的划分,从而导致差异。以下代码示例在使用x64目标的发布模式下编译时重复您的结果,并直接从命令提示符执行。我正在使用Visual Studio 2008和.NET 3.5。
public static void Main()
{
double result = 1.0f / new ProvideThree().Three;
double resultVirtual = 1.0f / new ProvideVirtualThree().Three;
double resultConstant = 1.0f / 3;
short parsedThree = short.Parse("3");
double resultParsed = 1.0f / parsedThree;
Console.WriteLine("Result of 1.0f / ProvideThree = {0}", result);
Console.WriteLine("Result of 1.0f / ProvideVirtualThree = {0}", resultVirtual);
Console.WriteLine("Result of 1.0f / 3 = {0}", resultConstant);
Console.WriteLine("Result of 1.0f / parsedThree = {0}", resultParsed);
Console.ReadLine();
}
public class ProvideThree
{
public short Three
{
get { return 3; }
}
}
public class ProvideVirtualThree
{
public virtual short Three
{
get { return 3; }
}
}
结果如下:
Result of 1.0f / ProvideThree = 0.333333333333333
Result of 1.0f / ProvideVirtualThree = 0.333333343267441
Result of 1.0f / 3 = 0.333333333333333
Result of 1.0f / parsedThree = 0.333333343267441
IL非常简单:
.locals init ([0] float64 result,
[1] float64 resultVirtual,
[2] float64 resultConstant,
[3] int16 parsedThree,
[4] float64 resultParsed)
IL_0000: ldc.r4 1. // push 1 onto stack as 32-bit float
IL_0005: newobj instance void Romeo.Program/ProvideThree::.ctor()
IL_000a: call instance int16 Romeo.Program/ProvideThree::get_Three()
IL_000f: conv.r4 // convert result of method to 32-bit float
IL_0010: div
IL_0011: conv.r8 // convert result of division to 64-bit float (double)
IL_0012: stloc.0
IL_0013: ldc.r4 1. // push 1 onto stack as 32-bit float
IL_0018: newobj instance void Romeo.Program/ProvideVirtualThree::.ctor()
IL_001d: callvirt instance int16 Romeo.Program/ProvideVirtualThree::get_Three()
IL_0022: conv.r4 // convert result of method to 32-bit float
IL_0023: div
IL_0024: conv.r8 // convert result of division to 64-bit float (double)
IL_0025: stloc.1
IL_0026: ldc.r8 0.33333333333333331 // constant folding
IL_002f: stloc.2
IL_0030: ldstr "3"
IL_0035: call int16 [mscorlib]System.Int16::Parse(string)
IL_003a: stloc.3 // store result of parse in parsedThree
IL_003b: ldc.r4 1.
IL_0040: ldloc.3
IL_0041: conv.r4 // convert result of parse to 32-bit float
IL_0042: div
IL_0043: conv.r8 // convert result of division to 64-bit float (double)
IL_0044: stloc.s resultParsed
前两种情况几乎完全相同。 IL首先将1作为32位浮点数推入堆栈,从两种方法中的一种获得3,将3转换为32位浮点数,执行除法,然后将结果转换为64位浮点数(双)。事实上(几乎)相同的IL - 唯一的区别是callvirt
与call
指令 - 在JIT中直接导致不同的结果点。
在第三种情况下,编译器已经将除法执行为常量。对于这种情况,不执行div
IL指令。
在最后一种情况下,我使用Parse
操作来最小化语句优化的可能性(我说“阻止”但我对编译器正在做的事情不太了解)。此案例的结果与virtual
调用的结果相匹配。似乎JIT正在优化非虚拟方法,或者它以不同的方式执行除法。
有趣的是,如果你删除了parsedThree
变量并且仅针对第四种情况resultParsed = 1.0f / short.Parse("3")
调用以下内容,则结果与第一种情况相同。同样,看起来JIT正在以不同的方式执行除法。
答案 1 :(得分:0)
我在.Net 4.5下测试了你的代码
在Visual Studio 2012中运行时,我总能得到相同的结果:
0.333333333333333在Rel / Dbg 32位中运行时
在Rel / Dbg 64位中运行时为0.333333343267441
我在运行exe时得到你的结果,而不是从视觉工作室的提示中启动它,只有当代码是:
优化代码选项没有任何区别。
我唯一能想到的是使用虚拟力稍后评估double类型,因此运行时使用浮点数执行1/3,然后将结果提升一倍,而不使用虚拟属性则提升操作数在进行操作之前直接加倍
答案 2 :(得分:0)
它可能是JITter优化而不是编译器优化。编译器在这里没有太多优化,但是JITter可以很容易地内联非虚拟版本并最终得到(double)1.0f / 3而不是(double)(1.0f / 3)。无论如何,您无法完全依赖浮点结果。