Kean Walmsley


  • About the Author
    Kean on Google+

August 2014

Sun Mon Tue Wed Thu Fri Sat
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31            








« Reflecting on AutoCAD .NET | Main | Adding to the AutoCAD pickfirst set with .NET »

January 26, 2007

One for the bit-twiddlers

In the last post we looked at some of the potential uses for the Reflector application.

I didn't end up elaborating on the third reason I stated for the Reflector being a compelling tool - that it can be used to help optimize code based on the resultant IL created. For fun I played around with using the Reflector to compare similarly structured code, and thought I'd use this post to share my approach and the results.

Firstly, though, I recommend taking a look at this useful primer on MSIL.

Now, let’s take some nearly identical code, and compare the output from each. I’m using Reflector for this, but other .NET disassembly tools are available: the most common one being ILDASM (presumably short for "IL DisASseMbler"), which ships as part of Visual Studio. Reflector is a nicer tool for my purposes, as it shows descriptions of individual IL instructions as you hover over them (an invaluable feature for those of us with imperfect knowledge/memories :-).

Here’s the code – the idea for this came from a comment from this post, where an esteemed colleague suggested that the IL created by try(), finally (dispose), is equivalent to that created by using() (and the source code using the latter approach is clearly more elegant).

To test out this assertion, I created two simple functions in C#, the only difference being that the first makes use of try(), finally(), while the second uses using():

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.Geometry;


namespace TestLibrary

{

  public class TestCommands

  {

    [CommandMethodAttribute("TEST")]

    static public void Version1()

    {

      Database db =

        HostApplicationServices.WorkingDatabase;

      Transaction tr =

        db.TransactionManager.StartTransaction();

      try

      {

        BlockTable bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId,

            OpenMode.ForRead

          );

        BlockTableRecord btr =

          (BlockTableRecord)tr.GetObject(

            bt[BlockTableRecord.ModelSpace],

            OpenMode.ForWrite

          );


        Circle cir =

          new Circle(

            new Point3d(50, 50, 0),

            new Vector3d(0, 0, 1),

            10

          );

        btr.AppendEntity(cir);

        tr.AddNewlyCreatedDBObject(cir, true);

        tr.Commit();

      }

      finally

      {

        tr.Dispose();

      }

    }


    [CommandMethodAttribute("TEST2")]

    static public void Version2()

    {

      Database db =

        HostApplicationServices.WorkingDatabase;

      using (

        Transaction tr =

          db.TransactionManager.StartTransaction()

        )

      {

        BlockTable bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId,

            OpenMode.ForRead

          );

        BlockTableRecord btr =

          (BlockTableRecord)tr.GetObject(

            bt[BlockTableRecord.ModelSpace],

            OpenMode.ForWrite

          );


        Circle cir =

          new Circle(

            new Point3d(50, 50, 0),

            new Vector3d(0, 0, 1),

            10

          );

        btr.AppendEntity(cir);

        tr.AddNewlyCreatedDBObject(cir, true);

        tr.Commit();

      }

    }

  }

}

Once the code is compiled into an assembly, I loaded it into Reflector, to compare the resultant IL of the respective functions.

Reflector_5_1

Here's the IL output from Reflector for Version1 - the function with try(), finally():

[ Note: copying and pasting the output into HTML maintains the tooltips with descriptions for the various IL operations - that's cool! ]

.method public hidebysig static void Version1() cil managed
{
      .custom instance void [acmgd]Autodesk.AutoCAD.Runtime.CommandMethodAttribute::.ctor(string) = ( string("TEST") )
      .maxstack 5
      .locals init (
            [0] [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Database database1,
            [1] [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Transaction transaction1,
            [2] [acdbmgd]Autodesk.AutoCAD.DatabaseServices.BlockTable table1,
            [3] [acdbmgd]Autodesk.AutoCAD.DatabaseServices.BlockTableRecord record1,
            [4] [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Circle circle1)
      L_0000: nop 
      L_0001: call [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Database [acdbmgd]Autodesk.AutoCAD.DatabaseServices.HostApplicationServices::get_WorkingDatabase()
      L_0006: stloc.0 
      L_0007: ldloc.0 
      L_0008: callvirt instance [acdbmgd]Autodesk.AutoCAD.DatabaseServices.TransactionManager [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Database::get_TransactionManager()
      L_000d: callvirt instance [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Transaction [acdbmgd]Autodesk.AutoCAD.DatabaseServices.TransactionManager::StartTransaction()
      L_0012: stloc.1 
      L_0013: nop 
      L_0014: ldloc.1 
      L_0015: ldloc.0 
      L_0016: callvirt instance [acdbmgd]Autodesk.AutoCAD.DatabaseServices.ObjectId [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Database::get_BlockTableId()
      L_001b: ldc.i4.0 
      L_001c: callvirt instance [acdbmgd]Autodesk.AutoCAD.DatabaseServices.DBObject [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Transaction::GetObject([acdbmgd]Autodesk.AutoCAD.DatabaseServices.ObjectId, [acdbmgd]Autodesk.AutoCAD.DatabaseServices.OpenMode)
      L_0021: castclass [acdbmgd]Autodesk.AutoCAD.DatabaseServices.BlockTable
      L_0026: stloc.2 
      L_0027: ldloc.1 
      L_0028: ldloc.2 
      L_0029: ldsfld string modopt([mscorlib]System.Runtime.CompilerServices.IsConst) modopt([mscorlib]System.Runtime.CompilerServices.IsConst) [acdbmgd]Autodesk.AutoCAD.DatabaseServices.BlockTableRecord::ModelSpace
      L_002e: callvirt instance [acdbmgd]Autodesk.AutoCAD.DatabaseServices.ObjectId [acdbmgd]Autodesk.AutoCAD.DatabaseServices.SymbolTable::get_Item(string)
      L_0033: ldc.i4.1 
      L_0034: callvirt instance [acdbmgd]Autodesk.AutoCAD.DatabaseServices.DBObject [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Transaction::GetObject([acdbmgd]Autodesk.AutoCAD.DatabaseServices.ObjectId, [acdbmgd]Autodesk.AutoCAD.DatabaseServices.OpenMode)
      L_0039: castclass [acdbmgd]Autodesk.AutoCAD.DatabaseServices.BlockTableRecord
      L_003e: stloc.3 
      L_003f: ldc.r8 50
      L_0048: ldc.r8 50
      L_0051: ldc.r8 0
      L_005a: newobj instance void [acdbmgd]Autodesk.AutoCAD.Geometry.Point3d::.ctor(float64, float64, float64)
      L_005f: ldc.r8 0
      L_0068: ldc.r8 0
      L_0071: ldc.r8 1
      L_007a: newobj instance void [acdbmgd]Autodesk.AutoCAD.Geometry.Vector3d::.ctor(float64, float64, float64)
      L_007f: ldc.r8 10
      L_0088: newobj instance void [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Circle::.ctor([acdbmgd]Autodesk.AutoCAD.Geometry.Point3d, [acdbmgd]Autodesk.AutoCAD.Geometry.Vector3d, float64)
      L_008d: stloc.s circle1
      L_008f: ldloc.3 
      L_0090: ldloc.s circle1
      L_0092: callvirt instance [acdbmgd]Autodesk.AutoCAD.DatabaseServices.ObjectId [acdbmgd]Autodesk.AutoCAD.DatabaseServices.BlockTableRecord::AppendEntity([acdbmgd]Autodesk.AutoCAD.DatabaseServices.Entity)
      L_0097: pop 
      L_0098: ldloc.1 
      L_0099: ldloc.s circle1
      L_009b: ldc.i4.1 
      L_009c: callvirt instance void [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Transaction::AddNewlyCreatedDBObject([acdbmgd]Autodesk.AutoCAD.DatabaseServices.DBObject, bool)
      L_00a1: nop 
      L_00a2: ldloc.1 
      L_00a3: callvirt instance void [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Transaction::Commit()
      L_00a8: nop 
      L_00a9: nop 
      L_00aa: leave.s L_00b6
      L_00ac: nop 
      L_00ad: ldloc.1 
      L_00ae: callvirt instance void [acdbmgd]Autodesk.AutoCAD.Runtime.DisposableWrapper::Dispose()
      L_00b3: nop 
      L_00b4: nop 
      L_00b5: endfinally 
      L_00b6: nop 
      L_00b7: ret 
      .try L_0013 to L_00ac finally handler L_00ac to L_00b6
}
And here's the output for Version2 - the one with using():

.method public hidebysig static void Version2() cil managed
{
      .custom instance void [acmgd]Autodesk.AutoCAD.Runtime.CommandMethodAttribute::.ctor(string) = ( string("TEST2") )
      .maxstack 5
      .locals init (
            [0] [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Database database1,
            [1] [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Transaction transaction1,
            [2] [acdbmgd]Autodesk.AutoCAD.DatabaseServices.BlockTable table1,
            [3] [acdbmgd]Autodesk.AutoCAD.DatabaseServices.BlockTableRecord record1,
            [4] [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Circle circle1,
            [5] bool flag1)
      L_0000: nop 
      L_0001: call [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Database [acdbmgd]Autodesk.AutoCAD.DatabaseServices.HostApplicationServices::get_WorkingDatabase()
      L_0006: stloc.0 
      L_0007: ldloc.0 
      L_0008: callvirt instance [acdbmgd]Autodesk.AutoCAD.DatabaseServices.TransactionManager [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Database::get_TransactionManager()
      L_000d: callvirt instance [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Transaction [acdbmgd]Autodesk.AutoCAD.DatabaseServices.TransactionManager::StartTransaction()
      L_0012: stloc.1 
      L_0013: nop 
      L_0014: ldloc.1 
      L_0015: ldloc.0 
      L_0016: callvirt instance [acdbmgd]Autodesk.AutoCAD.DatabaseServices.ObjectId [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Database::get_BlockTableId()
      L_001b: ldc.i4.0 
      L_001c: callvirt instance [acdbmgd]Autodesk.AutoCAD.DatabaseServices.DBObject [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Transaction::GetObject([acdbmgd]Autodesk.AutoCAD.DatabaseServices.ObjectId, [acdbmgd]Autodesk.AutoCAD.DatabaseServices.OpenMode)
      L_0021: castclass [acdbmgd]Autodesk.AutoCAD.DatabaseServices.BlockTable
      L_0026: stloc.2 
      L_0027: ldloc.1 
      L_0028: ldloc.2 
      L_0029: ldsfld string modopt([mscorlib]System.Runtime.CompilerServices.IsConst) modopt([mscorlib]System.Runtime.CompilerServices.IsConst) [acdbmgd]Autodesk.AutoCAD.DatabaseServices.BlockTableRecord::ModelSpace
      L_002e: callvirt instance [acdbmgd]Autodesk.AutoCAD.DatabaseServices.ObjectId [acdbmgd]Autodesk.AutoCAD.DatabaseServices.SymbolTable::get_Item(string)
      L_0033: ldc.i4.1 
      L_0034: callvirt instance [acdbmgd]Autodesk.AutoCAD.DatabaseServices.DBObject [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Transaction::GetObject([acdbmgd]Autodesk.AutoCAD.DatabaseServices.ObjectId, [acdbmgd]Autodesk.AutoCAD.DatabaseServices.OpenMode)
      L_0039: castclass [acdbmgd]Autodesk.AutoCAD.DatabaseServices.BlockTableRecord
      L_003e: stloc.3 
      L_003f: ldc.r8 50
      L_0048: ldc.r8 50
      L_0051: ldc.r8 0
      L_005a: newobj instance void [acdbmgd]Autodesk.AutoCAD.Geometry.Point3d::.ctor(float64, float64, float64)
      L_005f: ldc.r8 0
      L_0068: ldc.r8 0
      L_0071: ldc.r8 1
      L_007a: newobj instance void [acdbmgd]Autodesk.AutoCAD.Geometry.Vector3d::.ctor(float64, float64, float64)
      L_007f: ldc.r8 10
      L_0088: newobj instance void [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Circle::.ctor([acdbmgd]Autodesk.AutoCAD.Geometry.Point3d, [acdbmgd]Autodesk.AutoCAD.Geometry.Vector3d, float64)
      L_008d: stloc.s circle1
      L_008f: ldloc.3 
      L_0090: ldloc.s circle1
      L_0092: callvirt instance [acdbmgd]Autodesk.AutoCAD.DatabaseServices.ObjectId [acdbmgd]Autodesk.AutoCAD.DatabaseServices.BlockTableRecord::AppendEntity([acdbmgd]Autodesk.AutoCAD.DatabaseServices.Entity)
      L_0097: pop 
      L_0098: ldloc.1 
      L_0099: ldloc.s circle1
      L_009b: ldc.i4.1 
      L_009c: callvirt instance void [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Transaction::AddNewlyCreatedDBObject([acdbmgd]Autodesk.AutoCAD.DatabaseServices.DBObject, bool)
      L_00a1: nop 
      L_00a2: ldloc.1 
      L_00a3: callvirt instance void [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Transaction::Commit()
      L_00a8: nop 
      L_00a9: nop 
      L_00aa: leave.s L_00be
      L_00ac: ldloc.1 
      L_00ad: ldnull 
      L_00ae: ceq 
      L_00b0: stloc.s flag1
      L_00b2: ldloc.s flag1
      L_00b4: brtrue.s L_00bd
      L_00b6: ldloc.1 
      L_00b7: callvirt instance void [mscorlib]System.IDisposable::Dispose()
      L_00bc: nop 
      L_00bd: endfinally 
      L_00be: nop 
      L_00bf: ret 
      .try L_0013 to L_00ac finally handler L_00ac to L_00be
}
So what's different?

Both functions compile into IL comprising try() & finally() blocks. The "using" function (Version2) has an additional local variable declared at the top (flag1, in location 5), which is not explicitly used in our source code, but is clearly used for something in the IL.

The main difference is in the finally() block. Here's what we see in Version2:

      L_00ac: ldloc.1
      L_00ad: ldnull
      L_00ae: ceq
      L_00b0: stloc.s flag1
      L_00b2: ldloc.s flag1
      L_00b4: brtrue.s L_00bd
      L_00b6: ldloc.1
      L_00b7: callvirt instance void [mscorlib]System.IDisposable::Dispose()
      L_00bc: nop
      L_00bd: endfinally

Here's what it does, instruction by instruction:

L_00ac:   load the value of our transaction variable (tr in our source, transaction1 in the disassembled IL: the local variable in location 1) onto the evaluation stack
L_00ad:   load the value "null" onto the evaluation stack
L_00ae:  compare the top two values on the evaluation stack (are they equal?)
L_00b0:   store the result of this comparison in flag1 (aha!)
L_00b2:   push the value of flag1 back onto the eval stack
L_00b4:   if the value of flag1 is "true" (i.e. tr == null), then jump to the end
L_00b6:   load the value of the tr variable onto the eval stack
L_00b7:   call Dispose on the transaction

etc.

This is functionally equivalent to the code in Version1, other than we have no explicit check for whether tr is null (and if we add it, which is actually a good idea, the resultant code would look different - give it a try, if you're interested in looking into this further).

The other difference is that Version1 uses Autodesk.AutoCAD.Runtime.DisposableWrapper::Dispose(), while Version2 uses System.IDisposable::Dispose(). Which is ultimately also an insignificant difference.

Hopefully this simple example shows how helpful Reflector is in taking the lid off .NET assemblies and how it can further our understanding of the way various .NET languages get compiled into MSIL (and therefore get executed by the .NET Framework).

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/services/trackback/6a00d83452464869e200d834ddc70153ef

Listed below are links to weblogs that reference One for the bit-twiddlers:

blog comments powered by Disqus

10 Random Posts