Test1Test2虽然我总是用一个0参数调用方法,但是下面的内容始终快10%,所以切换案例中的内容 - 唯一的区别 - 永远不会被执行.
在将代码复制并粘贴到一个全新的项目中之后,仅将测试函数的名称更改为Main,结果是相反的.每次我运行该项目,Test2都会更快.
那么使这个更慢更快的因素是什么? 并且:我可以故意在.net中影响性能吗?
这些方法当然几乎没有做任何事情,因此对于主要涉及相同虚拟方法调用的测试(其中,CLR无法内联 - 对于Sun在bVM中实现它的开头语来说)的性能差异似乎很大.
NB这实际上是一个真实程序的最小版本,其中多个嵌套的switch语句导致巨大的性能差异不仅仅是10%,而是100%甚至更多,显然是由于嵌套交换机分支内部的代码存在,测试永远不会进入 (因此,这个最小版本可能会忽略可能涉及的实际程序的其他一些方面,但它确实会复制显着且一致的性能差异)
编辑在真实的程序中,可能比这个问题中的现象更重要的是,switch语句中的实际case语句是否适合通过分支表实现- (这取决于案例值中的空白 - 我是可以通过查看生成的IL代码来验证这一点)
测试运行
代码:
using System;
using System.Diagnostics;
class Test1 : ITest
{
public int Test(int a)
{
switch (a)
{
case 1: return a + a + a == 1234 ? 1 : 2;
case 2: return 2;
}
return 0;
}
}
class Test2 : ITest
{
public int Test(int a)
{
switch (a)
{
case 1: return 1;
case 2: return 2;
}
return 0;
}
}
class Program
{
static void Main(string[] args)
{
const long iterations = 200000000;
var test1 = new Test1();
var test2 = new Test2();
while (true)
{
var sw1 = Stopwatch.StartNew();
for (long i = 0; i < iterations; i++)
test1.Test(0);
sw1.Stop();
var sw2 = Stopwatch.StartNew();
for (long i = 0; i < iterations; i++)
test2.Test(0);
sw2.Stop();
var iterPerUsec1 = iterations / sw1.Elapsed.TotalMilliseconds / 1000;
var iterPerUsec2 = iterations / sw2.Elapsed.TotalMilliseconds / 1000;
Console.WriteLine("iterations per usec: " + (int) iterPerUsec1 + " / " + (int) iterPerUsec2 + " ratio: " + iterPerUsec1/iterPerUsec2);
}
}
}
interface ITest
{
int Test(int a);
}
Run Code Online (Sandbox Code Playgroud)
以下是典型运行的输出,其中Test1的速度实际上持续了12%以上:
iterations per usec: 369 / 342 ratio: 1.07656329512607
iterations per usec: 367 / 314 ratio: 1.16820632522335
iterations per usec: 372 / 337 ratio: 1.10255744679504
iterations per usec: 374 / 342 ratio: 1.09248387354978
iterations per usec: 367 / 329 ratio: 1.11451205881061
iterations per usec: 375 / 340 ratio: 1.10041698470293
iterations per usec: 373 / 314 ratio: 1.19033461920118
iterations per usec: 366 / 334 ratio: 1.09808424282708
iterations per usec: 372 / 314 ratio: 1.18497411681768
iterations per usec: 377 / 342 ratio: 1.10482425370152
iterations per usec: 380 / 346 ratio: 1.09794853154766
iterations per usec: 385 / 342 ratio: 1.12737583603649
iterations per usec: 376 / 327 ratio: 1.15024393718844
iterations per usec: 374 / 332 ratio: 1.12400483908544
iterations per usec: 383 / 341 ratio: 1.12106159857722
iterations per usec: 380 / 345 ratio: 1.10267634674555
iterations per usec: 375 / 344 ratio: 1.09211401775982
iterations per usec: 384 / 334 ratio: 1.14958454236246
iterations per usec: 368 / 321 ratio: 1.14575850263002
iterations per usec: 378 / 335 ratio: 1.12732301818235
iterations per usec: 380 / 338 ratio: 1.12375853123099
iterations per usec: 386 / 344 ratio: 1.12213818994067
iterations per usec: 385 / 336 ratio: 1.14346447712043
iterations per usec: 374 / 345 ratio: 1.08448615249764
...
Run Code Online (Sandbox Code Playgroud)
Han*_*ant 10
基准测试是一门艺术,很难可靠地测量这样快速的代码.通常,15%或更小的差异不是统计学上显着的结果.我只能评论发布的代码中的缺陷,这是一个非常常见的.典型的海森堡人,它是影响结果的测试本身.
第二个for()循环不像第一个for()循环那样优化.优化程序选择将哪些局部变量存储在CPU寄存器中引起的问题.特别是在32位程序中使用long时会出现问题,它会烧掉两个CPU寄存器.很可能,提到无法重现它的评论者使用x64抖动进行测试.
通过将测试移动到单独的方法中,可以减轻CPU寄存器分配器的压力:
static Stopwatch runTest1(Test1 test1, long iterations) {
var sw1 = Stopwatch.StartNew();
for (long i = 0; i < iterations; i++)
test1.Test(0);
sw1.Stop();
return sw1;
}
static Stopwatch runTest2(Test2 test2, long iterations) {
var sw2 = Stopwatch.StartNew();
for (long i = 0; i < iterations; i++)
test2.Test(0);
sw2.Stop();
return sw2;
}
static void Main(string[] args) {
const long iterations = 200000000;
var test1 = new Test1();
var test2 = new Test2();
while (true) {
var sw1 = runTest1(test1, iterations);
var sw2 = runTest2(test2, iterations);
// etc..
}
}
Run Code Online (Sandbox Code Playgroud)
而且你现在可以得到你所期望的.
通过查看生成的机器代码来确定此更改.工具>选项>调试>常规>取消勾选抑制JIT优化选项.然后,您可以使用Debug> Windows> Disassembly查看优化的机器代码.这显示了第一个for()循环:
for (long i = 0; i < iterations; i++)
00000089 xor ebx,ebx
0000008b xor esi,esi
0000008d mov ecx,dword ptr [esp+4]
00000091 xor edx,edx
00000093 call dword ptr ds:[04732844h]
00000099 add ebx,1
0000009c adc esi,0
0000009f test esi,esi
000000a1 jg 000000AD
000000a3 jl 0000008D
000000a5 cmp ebx,0BEBC200h
000000ab jb 0000008D
Run Code Online (Sandbox Code Playgroud)
第二个循环:
for (long i = 0; i < iterations; i++)
000000f5 mov dword ptr [esp+2Ch],0
000000fd xor ebx,ebx
000000ff mov ecx,dword ptr [esp]
00000102 xor edx,edx
00000104 call dword ptr ds:[056E28B8h]
0000010a mov eax,ebx
0000010c mov edx,dword ptr [esp+2Ch] ; <== here
00000110 add eax,1
00000113 adc edx,0
00000116 mov dword ptr [esp+2Ch],edx ; <== here
0000011a mov ebx,eax
0000011c cmp dword ptr [esp+2Ch],0
00000121 jg 0000012D
00000123 jl 000000FF
00000125 cmp ebx,0BEBC200h
0000012b jb 000000FF
Run Code Online (Sandbox Code Playgroud)
我标记了使它变慢的指令.第一个for()循环可以将循环变量存储在esi:ebx寄存器中并将它们保存在那里.这在第二个for()循环中不起作用,它耗尽了可用的CPU寄存器,并且循环变量的前32位必须存储在堆栈帧中.那很慢.
在这样的程序中进行更改不是通用建议,也不是通用解决方案.它恰好在这个具体案例中有效.只查看机器代码可以为您提供这样的更改可能有用的提示.这是关于手动调整代码所需要的.您实际上尝试优化的内容可能更多,此基准测试不太可能代表您的实际代码.
| 归档时间: |
|
| 查看次数: |
97 次 |
| 最近记录: |