我在笔记本电脑上运行了这个,64位Windows 8.1,2.2 Ghz Intel Core i3.代码是在发布模式下编译的,并且在没有附加调试器的情况下运行.
static void Main(string[] args)
{
calcMax(new[] { 1, 2 });
calcMax2(new[] { 1, 2 });
var A = GetArray(200000000);
var stopwatch = new Stopwatch();
stopwatch.Start(); stopwatch.Stop();
GC.Collect();
stopwatch.Reset();
stopwatch.Start();
calcMax(A);
stopwatch.Stop();
Console.WriteLine("caclMax - \t{0}", stopwatch.Elapsed);
GC.Collect();
stopwatch.Reset();
stopwatch.Start();
calcMax2(A);
stopwatch.Stop();
Console.WriteLine("caclMax2 - \t{0}", stopwatch.Elapsed);
Console.ReadKey();
}
static int[] GetArray(int size)
{
var r = new Random(size);
var ret = new int[size];
for (int i = 0; i < size; i++)
{
ret[i] = r.Next();
}
return ret;
}
static int calcMax(int[] A)
{
int max = int.MinValue;
for (int i = 0; i < A.Length; i++)
{
max = Math.Max(max, A[i]);
}
return max;
}
static int calcMax2(int[] A)
{
int max1 = int.MinValue;
int max2 = int.MinValue;
for (int i = 0; i < A.Length; i += 2)
{
max1 = Math.Max(max1, A[i]);
max2 = Math.Max(max2, A[i + 1]);
}
return Math.Max(max1, max2);
}
Run Code Online (Sandbox Code Playgroud)
以下是程序性能的统计数据(以毫秒为单位的时间):
框架2.0
X86平台: 2269(calcMax)2971(calcMax2) [获胜者calcMax]
X64平台: 6163(calcMax)5916(calcMax2) [获胜者calcMax2]
框架4.5(以毫秒为单位的时间)
X86平台: 2109(calcMax)2579(calcMax2) [获胜者calcMax]
X64平台: 2040(calcMax)2488(calcMax2) [获胜者calcMax]
如您所见,性能不同取决于框架和选择编译平台.我看到生成的IL代码,每种情况都是一样的.
calcMax2正在测试中,因为它应该使用处理器的"流水线".但是只有64位平台上的框架2.0才会更快.那么,在不同的表现中展示案例的真正原因是什么?
只是值得一提的一些注意事项.我的处理器(Haswell i7)与你的处理器不相符,我当然无法接近再现异常值x64的结果.
基准测试是一项危险的练习,很容易犯下可能对执行时间产生重大影响的简单错误.只有在查看生成的机器代码时才能真正看到它们.使用工具+选项,调试,常规并取消选中"抑制JIT优化"选项.这样,您可以使用Debug> Windows> Disassembly查看代码,而不会影响优化器.
当你这样做时你会看到一些东西:
你犯了一个错误,你实际上并没有使用方法返回值.抖动优化器在可能的情况下有这样的机会,它完全省略了max
calcMax()中的变量赋值.但不是在calcMax2()中.这是一个经典的基准测试oops,在一个真正的程序中,你当然会使用返回值.这使得calcMax()看起来太好了.
.NET 4抖动更灵活,可以优化Math.Max(),可以生成内联代码..NET 2抖动还不能这样做,它必须调用CLR辅助函数.因此,4.5测试应该运行大量的更快,它没有在哪一个强烈的暗示真正的节流代码执行.它不是处理器的执行引擎,而是访问内存的成本.您的阵列太大而无法放入处理器缓存中,因此您的程序陷入困境,等待缓慢的RAM提供数据.如果处理器不能与执行指令重叠,那么它就会停止.
值得注意的是calcMax()是C#执行的数组边界检查所发生的事情.抖动知道如何从循环中完全消除它.然而,它不够聪明,在calcMax2()中使用相同的A[i + 1]
螺丝.该检查不是免费的,它应该使calcMax2()相当慢.它不会再次强烈暗示记忆是真正的瓶颈.这是非常正常的btw,C#中的数组绑定检查可以有很低的开销,因为它比数组元素访问便宜得多.
至于你的基本任务,试图改善超标量执行机会,不,这不是处理器的工作方式.循环不是处理器的边界,它只是看到不同的比较和分支指令流,如果它们没有相互依赖性,所有这些指令都可以并发执行.你手工完成的是优化器已经完成的事情,一种叫做"循环展开"的优化.它选择不在这个特殊情况下这样做.抖动优化策略的概述中可以找到这篇文章.试图超越处理器和优化器是一个相当高的顺序,通过尝试帮助获得更糟糕的结果肯定不是不寻常的.