a = _b AndAlso _c _a = a
在它们之间的编译exe中,大约有6个机器周期,单个语句
_a = _b AndAlso _c
大约需要80个机器周期.这里_a,_b和_c是Form1的私有布尔变量,所讨论的语句是Form1的实例过程,其中a是局部布尔变量.
我找不到为什么单一的陈述需要这么久.我已经使用NetReflector探索它到CIL代码的水平,这看起来不错:
Instruction Explanation Stack 00: ldarg.0 Push Me (ref to current inst of Form1) Me 01: ldarg.0 Push Me Me,Me 02: ldfld bool Form1::_b Pop Me,read _b and push it _b,Me 07: brfalse.s 11 Pop _b; if false,branch to 11 Me 09: ldarg.0 (_b true) Push Me Me,Me 0a: ldfld bool Form1::_c (_b true) Pop Me,read _c and push it _c,Me 0f: brtrue.s 14 (_b true) Pop _c; if true,branch to 14 Me 11: ldc.i4.0 (_b,_c not both true) Push result 0 result,Me 12: br.s 15 Jump unconditionally to 15 result,Me ----- 14: ldc.i4.1 (_b,_c both true) Push result 1 result,Me 15: stfld bool Form1::_a Pop result and Me; write result to _a (empty) 1a:
任何人都可以说明为什么这个语句_a = _b AndAlso _c需要80个机器周期,而不是预测的5个左右?
我使用Windows XP与.NET 4.0和Visual Studio Express 2010.我用一个坦白的脏片段测量了我自己的时间,它基本上使用一个Stopwatch对象来对一个For-Next循环进行1000次迭代,包含有关代码,将其与空For-Next循环进行比较;它在两个循环中都包含一个无用的指令,以浪费几个周期并防止处理器停滞.粗鲁但足够我的目的.
首先是与AndAlso运算符关联的一般.它是一个短路操作符,如果左侧操作数的计算结果为False,则右侧的操作数不会得到评估.这需要机器代码中的分支.分支是处理器可以做的最慢的事情之一,它必须在分支机构前面猜测,以避免冲洗管道的风险.如果它猜测错了,那将会是一个重大的打击.非常好的覆盖在this post.如果一个变量是高度随机的,并且分支因此预测不足,典型的perf损失约为500%.
您可以通过使用And运算符来避免这种风险,而不需要机器代码中的分支.它只是一个指令,AND由处理器实现.在这样一个表达式中,没有任何意义上的赞同,如果右手操作数得到评估,没有任何问题.这里不适用,但即使IL显示分支,则抖动仍可能使CMOV指令(条件移动)使机器代码无分支.
但在你的情况下最重要的是Form类继承自MarshalByRefObject类.继承链是MarshalByRefObject>成分>控制> ScrollableControl> ContainerControl>形成.
MBRO由Just-in-Time编译器特别处理,代码可能正在使用类对象的代理,而真实对象生活在另一个AppDomain或另一台机器中.代理对于几乎任何类型的成员的抖动都是透明的,它们被实现为简单的方法调用.除了字段,它们不能被代理,因为对存储器读/写而不是方法调用进行了一个字段的访问.如果抖动不能证明该对象是本地的,则使用JIT_GetFieldXxx()和JIT_SetFieldXxx()的帮助方法强制调用该CLR. CLR知道对象引用是代理还是真正的交易,并处理差异.开销是相当大的,关于正确的80个循环.
只要变量是Form类的成员,就没有太多的可以做到这一点.将它们转换为辅助类是解决方法.