已编译 CIL 分析(第 1 部分)






1.14/5 (11投票s)
2006年10月7日
6分钟阅读

53935
分析 VS 2005 中 4 种语言的 .NET 编译器生成的 CIL
引言
在本文中,我们将探讨 Visual Studio 2005 中各种语言编译器生成的通用中间语言 (CIL)。
.NET Framework 是什么?
为简洁起见,这个问题的答案如下:.NET Framework 是一个可以添加到 Microsoft Windows 操作系统的软件组件(尽管目前正在努力使其能够在 Linux 和其他操作系统上运行)。.NET Framework 由一个类集合组成,这些类包含常见编程要求的预编码解决方案。为 .NET Framework 开发的应用程序在一个管理程序运行时要求的软件环境中运行。这个运行时环境被称为通用语言运行时 (CLR)。CLR 提供了应用程序虚拟机的外观,因此程序员无需考虑执行程序的特定 CPU 的功能。CLR 还提供其他重要服务,例如安全保证、内存管理和异常处理。
运行时还加快了开发人员的生产力。例如,程序员可以用他们选择的开发语言编写应用程序,同时充分利用运行时、类库以及其他开发人员用其他语言编写的组件。任何选择面向运行时的编译器供应商都可以这样做。面向 .NET Framework 的语言编译器使 .NET Framework 的功能可用于用该语言编写的现有代码,大大简化了现有应用程序的迁移过程。
- .NET Framework 最重要的组件在于通用语言基础结构 (CLI)。CLI 的目的是为应用程序开发提供一个与语言无关的平台。澄清一下,CLI 是一个规范,而不是一个实现,并且经常与 (CLR) 混淆
CLI 由 5 个主要部分组成
- 通用类型系统 (CTS)
- 通用语言规范 (CLS)
- 通用中间语言 (CIL)
- 即时编译器 (JIT)
- 虚拟执行系统 (VES)
详细讨论这 5 个部分超出了本文的范围。但是,我觉得需要对以下两个组件进行一些解释
- 通用语言规范 (CLS) - 是 Microsoft 为帮助编译器供应商提供的一组规范。这些规范规定了 .NET 语言必须具有的最小功能组。
- 通用中间语言 (CIL) - 是通用语言基础结构和 .NET Framework 中最低级别的可读编程语言。面向 .NET Framework 的语言编译为 CIL,然后汇编成字节码。CIL 类似于面向对象的汇编语言,并且完全基于堆栈。它由虚拟机执行。
- 即时编译器 (JIT) - (JIT) 编译使所有托管代码都能在执行它的系统的本地机器语言中运行。同时,内存管理器消除了内存碎片化的可能性,并提高了内存引用局部性,以进一步提高性能。
所有 .NET 语言都生而平等吗?
无论使用哪种 .NET 编程语言,语言编译器的输出都是 CIL 中相同逻辑的表示。在程序执行之前,CIL 被编译成适合执行程序的机器的对象代码。这个最后的编译步骤通常由 .NET Framework 的 CLR 组件在程序被调用时执行,尽管它可以在早期阶段手动执行。
概念
为了检查四个编译器编译的 CIL 是否确实存在差异,我用我的 Visual Studio 2005 版本(VB.NET、C#、C++ 和 J#)中提供的四种主要语言开发了四个相同的应用程序。我创建应用程序时遵循了完全相同的步骤。我将应用程序分成四个部分并加入了一些不好的习惯。我将所有这些应用程序编译,然后查看它们的 MSIL 编译器。
应用程序
我创建了一个控制台应用程序,它基本上包含一个秒表来计时应用程序执行了多长时间。应用程序本身基本上包含一个循环,该循环迭代一千次并连接一个字符串。字符串连接的结果打印到控制台,并打印出执行所需的时间长度。
我在所有这些应用程序中都省略了使用 `StringBuilder` 类,这是有意为之,因为这也是为了测试 `StringBuilder` 类的效率。如果您阅读本文的第 2 部分,您可以看到 `StringBuilder` 类提供的性能提升。
代码
VB.NET
Imports System.Diagnostics
Module Module1
Sub Main()
Dim strSomeString As String = ""
Dim sw As New Stopwatch
sw.Start()
For i As Integer = 0 To 1000
strSomeString += "Test "
Next
sw.Stop()
Console.WriteLine(strSomeString)
Console.WriteLine(sw.ElapsedMilliseconds)
Console.ReadLine()
End Sub
End Module
C#
using System;
using System.Diagnostics;
namespace ConsoleCSharp
{
class Program
{
static void Main(string[] args)
{
string strSomeString = "";
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 1000; i++)
{
strSomeString += "Test ";
}
sw.Stop();
Console.WriteLine(strSomeString);
Console.WriteLine(sw.ElapsedMilliseconds);
Console.ReadLine();
}
}
}
J#
package ConsoleJSharp;
import System.*;
import System.Diagnostics.*;
public class Program
{
public static void main(String[] args)
{
String strSomeString = "";
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 1000; i++)
{
strSomeString += "Test ";
}
sw.Stop();
Console.WriteLine(strSomeString);
Console.WriteLine(sw.get_ElapsedMilliseconds());
Console.ReadLine();
}
}
C++
#include "stdafx.h"
#using <mscorlib.dll>
using namespace System;
using namespace System::Diagnostics;
int main(array<System::String ^> ^args)
{
String^ strSomeString = "";
Stopwatch^ sw = gcnew Stopwatch;
sw->Start();
for (int i = 0;i< 1000 ;i++)
{
strSomeString += "Test ";
}
sw->Stop();
Console::WriteLine(strSomeString);
Console::WriteLine(sw->ElapsedMilliseconds);
Console::ReadLine();
}
已编译的 CIL
VB.NET
.method public static void Main() cil managed
{
.entrypoint
.custom instance void
[mscorlib]
System.STAThreadAttribute::.ctor()=(01 00 00 00)
// Code size 83 (0x53)
.maxstack 2
.locals init (
[0] string strSomeString,
[1] class [System]System.Diagnostics.Stopwatch sw,
[2] int32 i, [3] int32 VB$CG$t_i4$S0
)
IL_0000: nop
IL_0001: ldstr ""
IL_0006: stloc.0
IL_0007: newobj instance void
[System]
System.Diagnostics.Stopwatch::.ctor()
IL_000c: stloc.1
IL_000d: ldloc.1
IL_000e: callvirt instance void
[System]
System.Diagnostics.Stopwatch::Start()
IL_0013: nop
IL_0014: ldc.i4.0
IL_0015: stloc.2
IL_0016: ldloc.0
IL_0017: ldstr "Test "
IL_001c: call string
[mscorlib]
System.String::Concat(string, string)
IL_0021: stloc.0
IL_0022: nop
IL_0023: ldloc.2
IL_0024: ldc.i4.1
IL_0025: add.ovf
IL_0026: stloc.2
IL_0027: ldloc.2
IL_0028: ldc.i4 0x3e8
IL_002d: stloc.3
IL_002e: ldloc.3
IL_002f: ble.s IL_0016
IL_0031: ldloc.1
IL_0032: callvirt instance void
[System]
System.Diagnostics.Stopwatch::Stop()
IL_0037: nop
IL_0038: ldloc.0
IL_0039: call void
[mscorlib]
System.Console::WriteLine(string)
IL_003e: nop
IL_003f: ldloc.1
IL_0040: callvirt instance int64
[System]
System.Diagnostics.Stopwatch::get_ElapsedMilliseconds()
IL_0045: call void
[mscorlib]
System.Console::WriteLine(int64)
IL_004a: nop
IL_004b: call string
[mscorlib]
System.Console::ReadLine()
IL_0050: pop
IL_0051: nop
IL_0052: ret
} // end of method Module1::Main
C#
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 87 (0x57)
.maxstack 2
.locals init (
[0] string strSomeString,
[1] class [System]System.Diagnostics.Stopwatch sw,
[2] int32 i,
[3] bool CS$4$0000
)
IL_0000: nop
IL_0001: ldstr ""
IL_0006: stloc.0
IL_0007: newobj instance void
[System]
System.Diagnostics.Stopwatch::.ctor()
IL_000c: stloc.1
IL_000d: ldloc.1
IL_000e: callvirt instance void
[System]
System.Diagnostics.Stopwatch::Start()
IL_0013: nop
IL_0014: ldc.i4.0
IL_0015: stloc.2
IL_0016: br.s IL_002a
IL_0018: nop
IL_0019: ldloc.0
IL_001a: ldstr "Test "
IL_001f: call string
[mscorlib]
System.String::Concat(string,string)
IL_0024: stloc.0
IL_0025: nop
IL_0026: ldloc.2
IL_0027: ldc.i4.1
IL_0028: add
IL_0029: stloc.2
IL_002a: ldloc.2
IL_002b: ldc.i4 0x3e8
IL_0030: clt
IL_0032: stloc.3
IL_0033: ldloc.3
IL_0034: brtrue.s IL_0018
IL_0036: ldloc.1
IL_0037: callvirt instance void
[System]
System.Diagnostics.Stopwatch::Stop()
IL_003c: nop
IL_003d: ldloc.0
IL_003e: call void
[mscorlib]
System.Console::WriteLine(string)
IL_0043: nop
IL_0044: ldloc.1
IL_0045: callvirt instance int64
[System]
System.Diagnostics.Stopwatch::get_ElapsedMilliseconds()
IL_004a: call void
[mscorlib]
System.Console::WriteLine(int64)
IL_004f: nop
IL_0050: call string
[mscorlib]
System.Console::ReadLine()
IL_0055: pop
IL_0056: ret
} // end of method Program::Main
J#
.method public hidebysig static void main(string[]args)cil managed
{
.entrypoint
// Code size 108 (0x6c)
.maxstack 2
.locals init
(
[0] string strSomeString,
[1] class [System]System.Diagnostics.Stopwatch sw,
[2] int32 i
)
IL_0000: ldtoken [vjslib]
com.ms.vjsharp.lang.ObjectImpl
IL_0005: call void
[mscorlib]
System.Runtime.CompilerServices.
RuntimeHelpers::RunClassConstructor
(
valuetype [mscorlib]System.RuntimeTypeHandle
)
IL_000a: ldstr ""
IL_000f: stloc.0
IL_0010: newobj instance void
[System]
System.Diagnostics.Stopwatch::.ctor()
IL_0015: stloc.1
IL_0016: ldloc.1
IL_0017: callvirt instance void
[System]
System.Diagnostics.Stopwatch::Start()
IL_001c: ldc.i4.0
IL_001d: stloc.2
IL_001e: br.s IL_003f
IL_0020: newobj instance void
[vjslib]
java.lang.StringBuffer::.ctor()
IL_0025: ldloc.0
IL_0026: callvirt instance class
[vjslib]
java.lang.StringBuffer
[vjslib]
java.lang.StringBuffer::append(string)
IL_002b: ldstr "Test "
IL_0030: callvirt instance class
[vjslib]
java.lang.StringBuffer
[vjslib]
java.lang.StringBuffer::append(string)
IL_0035: callvirt instance string
[vjslib]
java.lang.StringBuffer::ToString()
IL_003a: stloc.0
IL_003b: ldloc.2
IL_003c: ldc.i4.1
IL_003d: add
IL_003e: stloc.2
IL_003f: ldloc.2
IL_0040: ldc.i4 0x3e8
IL_0045: blt.s IL_0020
IL_0047: ldloc.1
IL_0048: callvirt instance void
[System]
System.Diagnostics.Stopwatch::Stop()
IL_004d: ldloc.0
IL_004e: call void
[mscorlib]
System.Console::WriteLine(string)
IL_0053: ldloc.1
IL_0054: callvirt instance int64
[System]
System.Diagnostics.Stopwatch::get_ElapsedMilliseconds()
IL_0059: call void
[mscorlib]
System.Console::WriteLine(int64)
IL_005e: call string
[mscorlib]
System.Console::ReadLine()
IL_0063: pop
IL_0064: call void
[vjslib]
com.ms.vjsharp.util.Utilities::cleanupAfterMainReturns()
IL_0069: br.s IL_006b
IL_006b: ret
} // end of method Program::main
C++
.method assembly static int32 main(string[] args) cil managed
{
// Code size 83 (0x53)
.maxstack 2
.locals (
[0] class [System]System.Diagnostics.Stopwatch sw,
[1] string strSomeString,
[2] int32 i
)
IL_0000: ldnull
IL_0001: stloc.1
IL_0002: ldnull
IL_0003: stloc.0
IL_0004: ldstr ""
IL_0009: stloc.1
IL_000a: newobj instance void
[System]
System.Diagnostics.Stopwatch::.ctor()
IL_000f: stloc.0
IL_0010: ldloc.0
IL_0011: call instance void
[System]
System.Diagnostics.Stopwatch::Start()
IL_0016: ldc.i4.0
IL_0017: stloc.2
IL_0018: br.s IL_001e
IL_001a: ldloc.2
IL_001b: ldc.i4.1
IL_001c: add
IL_001d: stloc.2
IL_001e: ldloc.2
IL_001f: ldc.i4 0x3e8
IL_0024: bge.s IL_0034
IL_0026: ldloc.1
IL_0027: ldstr "Test "
IL_002c: call string
[mscorlib]
System.String::Concat
(
string,
string
)
IL_0031: stloc.1
IL_0032: br.s IL_001a
IL_0034: ldloc.0
IL_0035: call instance void
[System]
System.Diagnostics.Stopwatch::Stop()
IL_003a: ldloc.1
IL_003b: call void
[mscorlib]
System.Console::WriteLine(string)
IL_0040: ldloc.0
IL_0041: call instance int64
[System]
System.Diagnostics.Stopwatch::get_ElapsedMilliseconds()
IL_0046: call void
[mscorlib]
System.Console::WriteLine(int64)
IL_004b: call string
[mscorlib]
System.Console::ReadLine()
IL_0050: pop
IL_0051: ldc.i4.0
IL_0052: ret
} // end of method 'Global Functions'::main
注意事项
- 不同编译器生成的输出略有不同,尤其是在每个代码段的开头。
- VB 和 C# CIL 看起来非常相似。
- 我已经将编译器在方法开头声明所有局部变量的方式用粗体字标出
- VB 控制台向导生成的是模块,而不是类
为什么 VB 生成模块?
在 VB 开发人员社区中,关于模块是否是一种面向对象编程 (OOP) 方法存在很多讨论。一个常见的误解是模块是 VB6 的遗留物,因此应该不惜一切代价避免使用。尽管对这个热门话题的完整讨论超出了本文的范围,但我会指出 VB.NET 中的模块确实是面向对象的。您应该将模块视为一种特殊的类类型,不能用于创建对象。它只能包含共享成员;它不能包含实例成员。尽管模块的每个成员都隐式共享,但如果您在任何一个成员中添加 Shared 关键字,您将遇到编译时错误。
其他语言中不存在模块,那么 VB 有何特别之处?
.NET 2.0 中 C# 语言令人兴奋的新增功能之一是静态类的添加。静态类和类成员用于创建无需创建类实例即可访问的数据和函数。静态类成员可用于分离独立于任何对象身份的数据和行为:数据和函数不会因对象发生变化而改变。当类中没有依赖于对象身份的数据或行为时,可以使用静态类。因此,静态类可以被认为是 C# 对 VB 谦逊模块的回答。
代码有什么问题?
尽管这段代码乍一看似乎相当优化且功能正常,并且代码可以在不引起任何讨厌错误的情况下被其各自的编译器编译。本质上,这段代码并非真正的基于 .NET 的代码。如果你运行这段代码,你会发现简单的字符串连接例程可能需要 10 到 44 毫秒(毫秒)才能完成,无论你选择哪种语言运行。这可以优化吗,你还能看到其他什么问题?
在第 2 部分中,我将演示即使是这样一个简单的应用程序也可以如何优化。
来源
我的一些文章背景信息使用了从维基百科获得的关于 .NET Framework 的信息。
历史
- 06-10-06 草稿版 第 1 部分
- 09-10-06 略微更新以改进格式并更改标题
- 10-10-06 更新了一些背景信息