65.9K
CodeProject 正在变化。 阅读更多。
Home

分析 IL 并了解您的性能

starIconstarIconemptyStarIconemptyStarIconemptyStarIcon

2.00/5 (8投票s)

2004年9月4日

3分钟阅读

viewsIcon

50766

避免不必要的变量声明和冗余指令。在一个简单的例子中分析和理解编译器和生成的 IL 指令,从而节省你的性能。

引言

在本文中,我将尝试演示一个我很久以前就有的理论,并且一些和我交谈的人有时会忽略它。“请避免使用过多和不必要的私有变量——当你真的不需要它的时候!” 理论,或者说友好地称为 “不够清晰的代码” 理论。

首先,我不是英语母语者,也不是 .NET 专家,所以,请对我温柔一点 :)

问题所在

想象一下下面的代码

int result = Sum(value1,value2);
if(result>0)
    return true;
else
    return false;

你们中的大多数人都已经知道我的意思。为什么我们需要 result。是的,我们为什么需要它?我们不需要。但是有些人坚持这样做。通常,理由是“这样做更清楚,做一个 if 指令,更具可读性”。我知道……你们不是他们中的一员 ;) 。但是对于那些没有阅读这篇文章的人,我将明确解释为什么你应该避免不必要的代码,以及为什么你应该将其变成“return Sum(value1, value0)>0;”,甚至更好,变成“return value1+value0>0;”。

当然,本文不是关于如上所示的“确切”代码,而是关于避免不必要的堆栈内存使用,并真正理解原因。

那么真正的原因是什么?

首先,,抱歉,但它不够清晰!(有人不同意吗?)。我无法理解为什么有人会觉得这更清晰。如果有人没有阅读这篇文章,却这么认为,我建议也把变量名 result 重命名为 resultOfSomeSomeBetweenTwoValuesThatImGonnaSeeIfItIsGreatherThanZero。这样更清晰。

但说到底,有人知道这会在你的代码中产生什么结果吗?当然,你知道:不必要的堆栈内存消耗和冗余的运行时指令。

请考虑以下控制台应用程序

using System;

namespace TestStack
{
 class Class1
 {

   private static int Sum(int value1, int value2)
   {
     return value1 + value2;
   }

   private static bool AnnoyingIsResultGreaterThanZero 
                                (int value1, int value2 )
   {
     int result = Sum(value1,value2);
     if (result>0)
       return true;
     else
       return false;
   }

   private static bool IsResultGreaterThanZero(int value1, int value2 )
   {
     return Sum(value1,value2)>0;
   }

   [STAThread]
   static void Main(string[] args)
   {

     const int ITERATIONS = 2147483647;
     Console.WriteLine("Doing {0} iterations...",ITERATIONS);
     System.DateTime start ;
     bool result;

     //Warm up
     result  = IsResultGreaterThanZero(1,1);
     result = AnnoyingIsResultGreaterThanZero(1,1);
     result = (Sum(1,1)>0);
     result = (1+1>0);

     start = System.DateTime.UtcNow;
     Console.WriteLine("Calling the " + 
        "AnnoyingIsResultGreaterThanZero method...");

     for (int i=0 ; i<ITERATIONS ; i++)
     {
       result = AnnoyingIsResultGreaterThanZero(1,1);
     }
     Console.WriteLine("Total time taken with " + 
         "AnnoyingIsResultGreaterThanZero method : " + 
         (System.DateTime.UtcNow - start).ToString());

     start = System.DateTime.UtcNow;
     Console.WriteLine("Calling the IsResultGreaterThanZero method...");
     for (int i=0 ; i<ITERATIONS ; i++)
     {
       result  = IsResultGreaterThanZero(1,1);
     }
     Console.WriteLine("Total time taken with " + 
            "IsResultGreaterThanZero method : " + 
            (System.DateTime.UtcNow - start).ToString());

     start = System.DateTime.UtcNow;
     Console.WriteLine("Directly calling the Sum method ...");
     for (int i=0 ; i<ITERATIONS ; i++)
     {
       result = (Sum(1,1)>0);
     }
     Console.WriteLine("Total time taken with Sum method: " + 
                (System.DateTime.UtcNow - start).ToString());

     start = System.DateTime.UtcNow;
     Console.WriteLine("Directly making the sum ...");
     for (int i=0 ; i<ITERATIONS ; i++)
     {
       result = (1+1>0);
     }
     Console.WriteLine("Total time taken with directly sum : " + 
                   (System.DateTime.UtcNow - start).ToString());

     Console.ReadLine();
   }
 }
}

我们这里有一些基本的成员

  • Sum 返回两个整数的和,
  • AnnoyingIsResultGreaterThanZeroIsResultGreaterThanZero 返回 true,如果两个值的结果大于零,但 AnnoyingIsResultGreaterThanZero 将以冗余的方式执行;
  • Main 启动应用程序并运行一些性能测试,将所花费的时间写入控制台;

AnnoyingIsResultGreaterThanZeroIsResultGreaterThanZero 都产生相同的结果。虽然,两者在生成的 IL 中是截然不同的。

让我们看看由 IsResultGreaterThanZero 生成的 IL。要查看 IL,可以使用 SDK 中提供的 Reflector 或 ILDASM。(提示:使用 Reflector,当鼠标悬停在 IL 指令上时,你可以看到有关生成的 IL 的更多详细信息。)

.method private hidebysig static bool 
    IsResultGreaterThanZero(int32 value1, int32 value2) cil managed
{
      // Code Size: 11 byte(s)
      .maxstack 2
      L_0000: ldarg.0 
      L_0001: ldarg.1 
      L_0002: call int32 TestStack.Class1::Sum(int32, int32)
      L_0007: ldc.i4.0 
      L_0008: cgt 
      L_000a: ret 
}

这看起来非常简单的 IL。L_0000L_00001 将参数加载到计算堆栈中。接下来,我们调用 Sum,加载一个 0(要比较的值),并比较 Sum 返回的值和刚刚推入堆栈的值 (0),然后退出子程序。

当分析由 AnnoyingIsResultGreaterThanZero 生成的 IL 时,我们可以看到以下内容

.method private hidebysig static bool 
    AnnoyingIsResultGreaterThanZero(int32 value1, int32 value2) cil managed
{
      // Code Size: 28 byte(s)
      .maxstack 4
      .locals (
            int32 V_0,
            bool V_1)
      L_0000: ldarg.0 
      L_0001: ldarg.1 
      L_0002: call int32 TestStack.Class1::Sum(int32, int32)
      L_0007: stloc.0 
      L_0008: ldloc.0 
      L_0009: ldc.i4.0 
      L_000a: ble.s L_0013
      L_000c: ldc.i4.1 
      L_000d: stloc.1 
      L_000e: br L_001a
      L_0013: ldc.i4.0 
      L_0014: stloc.1 
      L_0015: br L_001a
      L_001a: ldloc.1 
      L_001b: ret 
}

正如你所看到的,这截然不同。首先,正如你所看到的,使用行 L_000aL_000d,我们需要更多内存来存储“if”求值的结果。其次,运行时有 10 条以上的指令需要求值。

运行应用程序并查看结果

我的测试环境是 PIV 3.06Mhz HT,1GB RAM,运行 .NET 1.1。应用程序在发布模式下编译,没有使用 /optimize 开关。结果是

Doing 2147483647 iterations...
Calling the AnnoyingIsResultGreaterThanZero method...
Total time taken with AnnoyingIsResultGreaterThanZero method : 00:00:11.5937500
Calling the IsResultGreaterThanZero method...
Total time taken with IsResultGreaterThanZero method : 00:00:01.5468750
Directly calling the Sum method ...
Total time taken with Sum method: 00:00:01.5312500
Directly making the sum ...
Total time taken with directly sum : 00:00:01.7187500

我进行了 2147483647 次迭代。我知道这很多,但通过这种测试用例,只有通过大量的迭代,我们才能得出一些结论。正如你所看到的,我们谈论的是 10 倍以上。

即使使用 /optimize 开关,我们仍然会得到更慢的结果,你可能会看到

Doing 2147483647 iterations...
Calling the 
AnnoyingIsResultGreaterThanZero method...
Total time taken with AnnoyingIsResultGreaterThanZero method : 00:00:02.2343750
Calling the IsResultGreaterThanZero method...
Total time taken with IsResultGreaterThanZero method : 00:00:01.4843750
Directly calling the Sum method ...
Total time taken with Sum method: 00:00:01.5000000
Directly making the sum ...
Total time taken with directly sum : 00:00:01.5000000

结论

对于我来说,这是一个非常冒险的话题,因为,正如我在之前的文章评论中所说,我不是 JIT 方面的专家。在之前的文章版本中,我犯了一些错误,正是因为这一点,也许我现在正在犯更严重的错误 . . . :) 请随时发表评论并帮助就此主题得出结论。

© . All rights reserved.