编程语言中的类型转换与性能优化

编程语言中,类型转换是一个常见的操作,它允许程序员在不同的数据类型之间进行转换。虽然许多程序员可能认为类型转换是一个简单的操作,但实际上,不同类型的转换对程序的性能有不同的影响。本文将分析类型转换的常见情况、编译器的行为以及MSIL(Microsoft Intermediate Language)生成的代码,以帮助程序员更好地理解类型转换对程序性能的影响,并提供一些优化建议。

原始类型的转换

原始类型是指那些可以直接由虚拟机指令处理的非组合类型,例如int、long、float等。这些类型没有内部结构,并且总是按值传递,除非程序员明确指定其他行为(使用out和ref修饰符)。下面是一个关于使用和转换原始类型的简单示例:

int z = 10; double r = 3.4; uint n = 20; r = z; // 隐式从int转换为double z = (int)r; // 显式从double转换为int n = (uint)z; // 显式从int转换为uint

这个示例在原始类型集合中执行了一些转换,在某些情况下将转换任务留给编译器,在其他情况下明确标记转换。接下来,将深入MSIL生成的代码,检查类型转换对代码的影响:

.locals init ([ 0] int32 z, [ 1] float32 r, [ 2] unsigned int32 n) IL_0000: ldc.i4.s 10 IL_0002: stloc.0 IL_0003: ldc.r4 (9A 99 59 40) IL_0008: stloc.1 IL_0009: ldc.i4.s 20 IL_000b: stloc.2 // (1) IL_000c: ldloc.0 IL_000d: conv.r4 IL_000e: stloc.1 IL_000f: ldloc.1 // (2) IL_0010: conv.i4 IL_0011: stloc.0 IL_0012: ldloc.0 // (3) IL_0013: stloc.2 IL_0014: ret

如所见,代码中有几个Conv.XY指令,其功能是将栈顶的值转换为操作码中指定的类型(r4、i4等)。从现在起,知道“无辜”的显式和隐式转换在原始类型之间生成指令,这些指令可以通过一致的类型使用来避免。同样的转换也适用于64位数据类型,如double、long和ulong。

向下转换对象引用

C#提供了两种方式来转换对象引用(注意,除非在上一节中研究的类型,否则所有类型都是引用类型):

object myClass = new MyClass(); ((MyClass)myClass).DoSome(); // (1) (myClass as MyClass).DoSome(); // (2)

前面的示例是向下转换(从类层次结构的顶部向下转换)的一个好例子。执行转换的方法看起来是一样的,但是生成的MSIL序列略有不同:

.locals init ([ 0] object myClass) IL_0000: newobj instance void Sample.MyClass::.ctor() IL_0005: stloc.0 IL_0006: ldloc.0 // (1) IL_0007: castclass Sample.MyClass IL_000c: callvirt instance void Sample.MyClass::DoSome() IL_0011: ldloc.0 // (2) IL_0012: isinst Sample.MyClass IL_0017: callvirt instance void Sample.MyClass::DoSome() IL_001c: ret

在第一行代码中,编译器发出一个Castclass操作码,如果可能的话,将引用转换为括号中指定的类型(如果不可以,则抛出InvalidCastException异常)。在第二种情况下,as运算符被翻译为IsInst操作码,它的工作速度更快,因为它只检查引用类型,而不执行任何类型的转换(也不抛出任何异常)。

向上转换对象引用

现在让做相反的事情!现在是时候爬到类层次结构的顶部,看看这些类型的转换有多慢(或多快)。以下示例创建了一个MyDerivedClass类型的对象,并将它的引用存储在一个MyClass类型的变量中:

MyDerivedClass myDerivedClass = new MyDerivedClass(); MyClass myClass = myDerivedClass;

产生的代码是:

.locals init ([ 0] class Sample.MyDerivedClass myDerivedClass, [ 1] class Sample.MyClass myClass) IL_0000: newobj instance void Sample.MyDerivedClass::.ctor() IL_0005: stloc.0 IL_0006: ldloc.0 IL_0007: stloc.1 IL_0008: ret

如所见,没有转换操作码,只有引用加载和存储。这对效率目的来说是好事...正如预期的那样,向上转换类型检查是在编译时进行的,运行时成本就像一个简单的变量分配一样便宜。

自定义转换运算符

C#语言包含了一个很棒的特性,允许定义隐式和显式转换运算符。这些转换方法的效率取决于转换方法的实现。无论如何,这些函数总是静态的,并且只有一个参数,所以过程调用开销很小(不需要传递"this"参数)。无论如何,似乎微软C#编译器没有内联这些方法,所以在栈中安排参数和返回地址可能会减慢代码执行速度。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485