|
|||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionExplicit and implicit type casting is a common programming topic for almost any imperative programming language. Most C, C++, or Pascal programmers care about efficiency and speed of their code; but those who use managed programming environments, such as Java, Visual Basic, or C# rely all the optimizing tasks on the compiler and the runtime environment. This can be a good approach in many cases, but managed languages are becoming more and more popular also for high-performance applications where the knowledge of the language, compiler, and runtime environment can enhance a program's quality and speed. This article analyzes the most common type casting situations and the compiler behavior in them. We are going to study the MSIL generated code, but not the machine-specific instruction sequences due to the implementation and vendor dependency. Casting primitive typesPrimitive types are those non-composed types which can be handled directly by the (virtual) machine instructions, i.e., int z = 10;
double r = 3.4;
uint n = 20;
r = z; // Implicit conversion from int to double (1)
z = (int)r; // Explicit conversion from double to int (2)
n = (uint)z; // Explicit conversion from int to uint (3)
This sample performs some conversions in the set of primitive types, leaving in some cases the casting tasks to the compiler and marking conversions explicitly in some other cases. OK, time to dive into the MSIL generated code and check the impact of type casts in our code: .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
As we can see, there are several Note that the last type cast doesn't need an explicit " A special kind of primitive type is Downcasting object referencesC# provides two ways for casting object references (note that all types, unless those studied in the previous section, are reference types): object myClass = new MyClass();
((MyClass)myClass).DoSome(); //(1)
(myClass as MyClass).DoSome(); //(2)
The previous is a good example of downcasting (casting from the top to the bottom of the class hierarchy). The method used to perform the cast appears to be the same, but the generated MSIL sequences are a bit different: .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
In the first line of code, the compiler emits a " In the second case, the In performance terms, we prefer the second option, because the "
In the other hand, parenthesized casts give a better error control to programmers, avoiding the null-reference errors obtained when invalid typecasts happen using the " Upcasting object referencesLet's make the opposite! Now it's time for climbing up into the class hierarchy, and see how slow (or fast) are these sort of casts. The following example creates an object of the type MyDerivedClass myDerivedClass = new MyDerivedClass();
MyClass myClass = myDerivedClass;
And the produced code is: .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
As we can see, there are no conversion opcodes, just reference loading and storing. This is good for out efficiency purposes... as expected, upcasting type checks are made at compile time and the runtime costs are as cheap as a simple assign between variables of the same type. Casting operatorsC# language contains a great feature which allows to define implicit and explicit conversion operators. The efficiency of these casting methods depends on the casting method implementation. Anyway, these functions are always static and have only one parameter, so the procedure call overhead is small (no " Putting it all togetherHere are some general tips for optimizing your programs based on the results obtained in the previous sections:
Tools usedAll the tests and disassemblies have been made using the tools included in the .NET Framework SDK. ILDasm can tell you much about your program's performance flaws, so play with it.
|
||||||||||||||||||||||||||||||