我把这段简单的代码输入到LINQPad 7中,看看哪段代码更快。我惊讶地发现第二个计时循环一直都很慢?有人能解释一下为什么吗?
void Main()
{
const byte hfm = 0b0001_0000;
UInt16 operand1 = 0x2;
UInt16 operand2 = 0xFFFE;
UInt32 result = (UInt32)(operand1 + operand2);
UInt32 hf;
var timer = new Stopwatch();
timer.Start();
for (var i = 0; i < 500_000_000; i++)
{
hf = (((operand1 ^ result ^ operand2) >> 8) & hfm);
}
timer.Stop();
timer.Elapsed.Dump("(((operand1 ^ result ^ operand2) >> 8) & hfm)");
timer.Restart();
for (var i = 0; i < 500_000_000; i++)
{
hf = (result >> 8) & hfm;
}
timer.Stop();
timer.Elapsed.Dump("(result >> 8) & hfm");
}
结果非常一致。第二段(更短更简单)代码运行速度慢了0. 03秒。
(((operand1 ^ result ^ operand2) >> 8) & hfm)
00:00:01.1278598
(result >> 8) & hfm
00:00:01.1612988
如果我从两个代码中删除“& hfm”,第二个代码保持相同的时间,但第一个代码比第二个代码慢约0.002秒;如果我从两个代码中删除“〉〉8”,第一个代码比第二个代码快约0.01秒。
根据LINQPad,为两个代码块生成的IL为hf = (((operand1 ^ result ^ operand2) >> 8) & hfm);
IL_0022 ldloc.0 // operand1
IL_0023 ldloc.2 // result
IL_0024 xor
IL_0025 ldloc.1 // operand2
IL_0026 xor
IL_0027 ldc.i4.8
IL_0028 shr.un
IL_0029 ldc.i4.s 10 // 16
IL_002B and
IL_002C stloc.3 // hf
hf = (result >> 8) & hfm;
IL_0073 ldloc.2 // result
IL_0074 ldc.i4.8
IL_0075 shr.un
IL_0076 ldc.i4.s 10 // 16
IL_0078 and
IL_0079 stloc.3 // hf
看起来第二个代码块肯定会更快...
UPDATE原来LINQPad是在编译调试代码(这是有道理的)当我把(Edit/Preferences/Query)改为“Compile for release..."时,两个代码块的时序非常相似。
2条答案
按热度按时间h4cxqtbf1#
这两个表达式之间的性能差异很可能是由于执行代码的处理器的微架构细节。现代CPU具有复杂的流水线、多个执行单元和许多优化,这些都可能导致非直观的性能特征。
把CPU想象成高科技厨房里的超高效厨师,以 lightning 般的速度处理各种任务,你给我们厨师的两份菜谱(两个表情),食材和烹饪步骤都不一样。
第一道食谱
(((operand1 ^ result ^ operand2) >> 8) & hfm)
的原料和步骤都比较多,但我们的星星厨师是多任务处理高手。他们可以同时切洋葱、拌沙拉和翻煎饼!这就像CPU能够交叉执行多条指令并并行执行它们一样。因此,尽管第一道食谱看起来更复杂,但我们熟练的厨师可以毫不费力地完成它!另一方面,第二道食谱
(result >> 8) & hfm
的原料和步骤较少,但它们需要完成的顺序却让我们的主厨慢了下来。这就像用一条腿跳着玩杂耍一样--不是不可能,但可能需要更多的时间才能做好!要点是,你不能总是根据表面的复杂程度来判断一个配方(或表达式)。就像我们的厨师一样,CPU是处理任务的大师,有时候,由于指令级并行的魔力,看起来更复杂的表达式可能最终会更快。
不过,请记住,执行时间的差异是微小的,就像你在10加仑的汤里加了多少盐一样,它是存在的,但可能不会对事情的总体规划产生巨大的影响。
lsmepo6l2#
确保使用适当的工具和技术进行基准测试。首先在发布模式下运行基准测试代码。
还可以考虑使用BenchmarkDotNet-一个领先的工具(微)基准测试.NET代码,它有很多有用的功能,也可以防止至少一些常见的错误(例如,它警告使用调试模式和/或运行附加的调试器)。
根据我的实验,这两种方法显示出相似的运行时间:
在我的计算机上:
| 方法|平均值|错误|标准差|
| - ------|- ------|- ------|- ------|
| 第一次|2.376美国|0.0129美国|0.0121美国|
| 第二次|2.365美国|0.0083美国|0.0074美国|
请注意,对于如此接近的微基准测试结果,尽管所有库都试图缓解,但仍有许多事情可能会干扰结果(例如,我在笔记本电脑上运行,这可能会限制CPU,另一个进程也可能会争夺资源,等等)