Author Topic: Witches, Ghosts, and Transactions  (Read 8165 times)

0 Members and 1 Guest are viewing this topic.

TheMaster

  • Guest
Witches, Ghosts, and Transactions
« on: April 10, 2013, 04:04:38 AM »
Code - C#: [Select]
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Diagnostics;
  6. using Autodesk.AutoCAD.Runtime;
  7. using Autodesk.AutoCAD.ApplicationServices;
  8. using Autodesk.AutoCAD.DatabaseServices;
  9. using Autodesk.AutoCAD.EditorInput;
  10. using Autodesk.AutoCAD.Geometry;
  11.  
  12. /// This code serves to debunk the myth that
  13. /// the OpenClosTransaction is 'faster' than
  14. /// a standard Transaction. The fact is that
  15. /// it is not significantly-faster when the
  16. /// amount of data involved is large.
  17. ///
  18. /// And, when the amount of data involved is
  19. /// relatively-small, the question of which
  20. /// is faster, is entirely moot.
  21.  
  22. namespace TransactionTest
  23. {
  24.    public static class TestTransactionCommands
  25.    {
  26.       /// <summary>
  27.       /// Test standard transaction (AcTrTransaction)
  28.       /// </summary>
  29.      
  30.       [CommandMethod( "TT1" )]
  31.       public static void TestAcTrTransaction()
  32.       {
  33.          Document doc = Application.DocumentManager.MdiActiveDocument;
  34.          Database db = doc.Database;
  35.          Editor ed = doc.Editor;
  36.  
  37.          using( Transaction tr = doc.TransactionManager.StartTransaction() )
  38.          {
  39.             BlockTableRecord btr = (BlockTableRecord)
  40.                tr.GetObject( db.CurrentSpaceId, OpenMode.ForRead );
  41.  
  42.             RXClass dbPointClass = RXClass.GetClass( typeof( DBPoint ) );
  43.  
  44.             int i = 0;
  45.             Stopwatch sw = Stopwatch.StartNew();
  46.             foreach( ObjectId id in btr )
  47.             {
  48.                if( id.ObjectClass == dbPointClass )
  49.                {
  50.                   DBPoint p = (DBPoint) tr.GetObject( id, OpenMode.ForRead );
  51.                   ++i;
  52.                }
  53.             }
  54.             sw.Stop();
  55.             ed.WriteMessage( "\nAcTrTransaction: {0}", sw.ElapsedMilliseconds );
  56.             tr.Commit();
  57.          }
  58.       }
  59.  
  60.       /// <summary>
  61.       /// Test OpenCloseTransaction
  62.       /// </summary>
  63.      
  64.       [CommandMethod( "TT2" )]
  65.       public static void TestOpenCloseTransaction()
  66.       {
  67.          Document doc = Application.DocumentManager.MdiActiveDocument;
  68.          Database db = doc.Database;
  69.          Editor ed = doc.Editor;
  70.  
  71.          using( Transaction tr = doc.TransactionManager.StartOpenCloseTransaction() )
  72.          {
  73.             BlockTableRecord btr = (BlockTableRecord)
  74.                tr.GetObject( db.CurrentSpaceId, OpenMode.ForRead );
  75.  
  76.             RXClass dbPointClass = RXClass.GetClass( typeof( DBPoint ) );
  77.  
  78.             int i = 0;
  79.             Stopwatch sw = Stopwatch.StartNew();
  80.             foreach( ObjectId id in btr )
  81.             {
  82.                if( id.ObjectClass == dbPointClass )
  83.                {
  84.                   DBPoint p = (DBPoint) tr.GetObject( id, OpenMode.ForRead );
  85.                   ++i;
  86.                }
  87.             }
  88.             sw.Stop();
  89.             ed.WriteMessage( "\nOpenCloseTransaction: {0}", sw.ElapsedMilliseconds );
  90.             tr.Commit();
  91.          }
  92.       }
  93.  
  94.       /// <summary>
  95.       /// Command to generate random DBPoints used as test data
  96.       /// </summary>
  97.  
  98.       [CommandMethod( "RNDPOINTS" )]
  99.       public static void RndPoints()
  100.       {
  101.          PromptDoubleOptions pdo = new PromptDoubleOptions( "\nNumber of points: " );
  102.          pdo.AllowZero = false;
  103.          pdo.AllowNegative = false;
  104.          Document doc = Application.DocumentManager.MdiActiveDocument;
  105.          Editor ed = doc.Editor;
  106.          PromptDoubleResult pdr = doc.Editor.GetDouble( pdo );
  107.          if( pdr.Status != PromptStatus.OK )
  108.             return;
  109.          int cnt = Convert.ToInt32( pdr.Value );
  110.          using( Transaction tr = doc.TransactionManager.StartTransaction() )
  111.          {
  112.             BlockTableRecord btr = (BlockTableRecord)
  113.                doc.Database.CurrentSpaceId.GetObject( OpenMode.ForWrite );
  114.             var points = GetRandomPoints( cnt, 10000.0 );
  115.             foreach( Point3d pt in points )
  116.             {
  117.                DBPoint dbp = new DBPoint( pt );
  118.                btr.AppendEntity( dbp );
  119.                tr.AddNewlyCreatedDBObject( dbp, true );
  120.             }
  121.             tr.Commit();
  122.          }
  123.       }
  124.  
  125.       /// <summary>
  126.       /// Get an array of random Point3d dispersed in a
  127.       /// Cube or 2d square whose side length = scale.
  128.       /// If flat is true, all resulting points are in
  129.       /// the XY plane.
  130.       /// </summary>
  131.  
  132.       static IEnumerable<Point3d> GetRandomPoints( long count, double scale, bool flat = false )
  133.       {
  134.          Random rnd = new Random();
  135.          if( flat )
  136.          {
  137.             for( long i = 0; i < count; i++ )
  138.             {
  139.                yield return new Point3d(
  140.                   rnd.NextDouble() * scale,
  141.                   rnd.NextDouble() * scale,
  142.                   0.0 );
  143.             }
  144.          }
  145.          else
  146.          {
  147.             for( long i = 0; i < count; i++ )
  148.             {
  149.                yield return new Point3d(
  150.                   rnd.NextDouble() * scale,
  151.                   rnd.NextDouble() * scale,
  152.                   rnd.NextDouble() * scale );
  153.             }
  154.          }
  155.       }
  156.    }
  157. }
  158.  

Here's mine:

Code - Text: [Select]
  1. Command: RNDPOINTS
  2. Number of points: 500000
  3. Command: TT1
  4. AcTrTransaction: 1522
  5. Command: TT2
  6. OpenCloseTransaction: 1434
  7. Command: TT1
  8. AcTrTransaction: 1584
  9. Command: TT2
  10. OpenCloseTransaction: 1425
  11. Command: TT1
  12. AcTrTransaction: 1505
  13. Command: TT2
  14. OpenCloseTransaction: 1430
  15.  

BlackBox

  • King Gator
  • Posts: 3770
Re: Witches, Ghosts, and Transactions
« Reply #1 on: April 10, 2013, 09:16:15 AM »
... Here's mine:

Code - Text: [Select]
  1. Command: rndpoints
  2. Number of points: 500000
  3. Command: tt1
  4. AcTrTransaction: 2004
  5. Command: tt2
  6. OpenCloseTransaction: 2858
  7. Command: tt1
  8. AcTrTransaction: 1881
  9. Command: tt2
  10. OpenCloseTransaction: 2849
  11. Command: tt1
  12. AcTrTransaction: 1933
  13. Command: tt2
  14. OpenCloseTransaction: 2853
  15.  

Using Dell Precision M6600, [Quad-]Core i7 @ 2.3 Ghz, 16GB RAM, Win7 Enterprise 64-Bit, tested in Civil 3D 2011.
"How we think determines what we do, and what we do determines what we get."

TheMaster

  • Guest
Re: Witches, Ghosts, and Transactions
« Reply #2 on: April 10, 2013, 01:06:08 PM »
... Here's mine:

Code - Text: [Select]
  1. Command: rndpoints
  2. Number of points: 500000
  3. Command: tt1
  4. AcTrTransaction: 2004
  5. Command: tt2
  6. OpenCloseTransaction: 2858
  7. Command: tt1
  8. AcTrTransaction: 1881
  9. Command: tt2
  10. OpenCloseTransaction: 2849
  11. Command: tt1
  12. AcTrTransaction: 1933
  13. Command: tt2
  14. OpenCloseTransaction: 2853
  15.  

Using Dell Precision M6600, [Quad-]Core i7 @ 2.3 Ghz, 16GB RAM, Win7 Enterprise 64-Bit, tested in Civil 3D 2011.

BlackBox, thanks.  If I'm not mistaken, in earlier releases of AutoCAD, the OpenCloseTransaction was implemented entirely in managed code, which would explain the lop-sided result you're getting. I'm not sure what release it was, that they moved it to native code, but as of 2012 it is.
« Last Edit: April 10, 2013, 01:09:30 PM by TT »

BlackBox

  • King Gator
  • Posts: 3770
Re: Witches, Ghosts, and Transactions
« Reply #3 on: April 10, 2013, 01:12:12 PM »
... Here's mine:

Code - Text: [Select]
  1. Command: rndpoints
  2. Number of points: 500000
  3. Command: tt1
  4. AcTrTransaction: 2004
  5. Command: tt2
  6. OpenCloseTransaction: 2858
  7. Command: tt1
  8. AcTrTransaction: 1881
  9. Command: tt2
  10. OpenCloseTransaction: 2849
  11. Command: tt1
  12. AcTrTransaction: 1933
  13. Command: tt2
  14. OpenCloseTransaction: 2853
  15.  

Using Dell Precision M6600, [Quad-]Core i7 @ 2.3 Ghz, 16GB RAM, Win7 Enterprise 64-Bit, tested in Civil 3D 2011.

BlackBox, thanks.  If I'm not mistaken, in earlier releases of AutoCAD, the OpenCloseTransaction was implemented entirely in managed code, which would explain the lop-sided result you're getting. I'm not sure what release it was, that they moved it to native code, but as of 2012 it is.

It must be the difference between 2011, and 2012, as here are the results in Civil 3D 2012 (same laptop):

Code - Text: [Select]
  1. Command: RNDPOINTS
  2. Number of points: 500000
  3. Command: tt1
  4. AcTrTransaction: 1298
  5. Command: tt2
  6. OpenCloseTransaction: 1152
  7. Command: tt1
  8. AcTrTransaction: 1219
  9. Command: tt2
  10. OpenCloseTransaction: 1137
  11. Command: tt1
  12. AcTrTransaction: 1215
  13. Command: tt2
  14. OpenCloseTransaction: 1137
  15.  
"How we think determines what we do, and what we do determines what we get."

Jeff H

  • Needs a day job
  • Posts: 6150
Re: Witches, Ghosts, and Transactions
« Reply #4 on: April 10, 2013, 02:11:14 PM »
Thanks Tony,
Only did 5000 because profiler makes it take much longer.
 
I ran the commands TT1 and TT2 to get jitting out of the way and when the times more than double is because I enabled the profiler, also for last 2 I forced a GC through the profiler before each command
Code - Text: [Select]
  1. Command: RNDPOINTS
  2. Number of points: 5000
  3. Command: TT1
  4. AcTrTransaction: 111
  5. Command: TT2
  6. OpenCloseTransaction: 92
  7. Command: TT1
  8. AcTrTransaction: 111
  9. Command: TT2
  10. OpenCloseTransaction: 109
  11. Command: TT1
  12. AcTrTransaction: 344
  13. Command: TT2
  14. OpenCloseTransaction: 343
  15.  
  16.  

 
I actually tried these just yesterday to see difference between them and to see difference of using open/close calls but in the scope of a transaction so Undo functionality would be available. Did them again on same drawing as above.
Code - C#: [Select]
  1.         [CommandMethod("TransactionMethod")]
  2.         public void TransactionMethod()
  3.         {
  4.             using (Transaction trx = Db.TransactionManager.StartTransaction())
  5.             {
  6.                 BlockTable bt = (BlockTable)trx.GetObject(Db.BlockTableId, OpenMode.ForRead, false, true);
  7.                 BlockTableRecord btr = (BlockTableRecord)trx.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead, false, true);
  8.                 foreach (ObjectId id in btr)
  9.                 {
  10.                     DBObject dbo = trx.GetObject(id, OpenMode.ForWrite, false, true);
  11.                     dbo.Erase(true);
  12.                 }
  13.                 foreach (ObjectId id in btr.IncludingErased)
  14.                 {
  15.                     DBObject dbo = trx.GetObject(id, OpenMode.ForWrite, true, true);
  16.                     dbo.Erase(false);
  17.                 }
  18.                 trx.Commit();
  19.             }
  20.         }
  21.         [CommandMethod("OpenCloseTransactionMethod")]
  22.         public void OpenCloseTransactionMethod()
  23.         {
  24.             using (OpenCloseTransaction trx = Db.TransactionManager.StartOpenCloseTransaction())
  25.             {
  26.                 BlockTable bt = (BlockTable)trx.GetObject(Db.BlockTableId, OpenMode.ForRead, false, true);
  27.                 BlockTableRecord btr = (BlockTableRecord)trx.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead, false, true);
  28.                 foreach (ObjectId id in btr)
  29.                 {
  30.                     DBObject dbo = trx.GetObject(id, OpenMode.ForWrite, false, true);
  31.                     dbo.Erase(true);
  32.                 }
  33.                 foreach (ObjectId id in btr.IncludingErased)
  34.                 {
  35.                     DBObject dbo = trx.GetObject(id, OpenMode.ForWrite, true, true);
  36.                     dbo.Erase(false);
  37.                 }
  38.                 trx.Commit();
  39.             }
  40.         }
  41.         [CommandMethod("OpenCloseMethod")]
  42.         public void OpenCloseMethod()
  43.         {
  44.             using (BlockTable bt = Db.BlockTableId.Open(OpenMode.ForRead, false, true) as BlockTable)
  45.             {
  46.                 using (BlockTableRecord btr = bt[BlockTableRecord.ModelSpace].Open(OpenMode.ForRead, false, true) as BlockTableRecord)
  47.                 {
  48.                     foreach (ObjectId id in btr)
  49.                     {
  50.                         using (DBObject dbo = id.Open(OpenMode.ForWrite, false, true))
  51.                         {
  52.                             dbo.Erase(true);
  53.                         }                        
  54.                     }
  55.                     foreach (ObjectId id in btr.IncludingErased)
  56.                     {
  57.                         using (DBObject dbo = id.Open(OpenMode.ForWrite, true, true))
  58.                         {
  59.                             dbo.Erase(false);
  60.                         }
  61.                     }
  62.                 }
  63.             }
  64.         }
  65.         [CommandMethod("OpenCloseMethodTransactionWrapped")]
  66.         public void OpenCloseMethodTransactionWrapped()
  67.         {
  68.             using (Transaction trx = Db.TransactionManager.StartTransaction())
  69.             {
  70.                 using (BlockTable bt = Db.BlockTableId.Open(OpenMode.ForRead, false, true) as BlockTable)
  71.                 {
  72.                     using (BlockTableRecord btr = bt[BlockTableRecord.ModelSpace].Open(OpenMode.ForRead, false, true) as BlockTableRecord)
  73.                     {
  74.                         foreach (ObjectId id in btr)
  75.                         {
  76.                             using (DBObject dbo = id.Open(OpenMode.ForWrite, false, true))
  77.                             {
  78.                                 dbo.Erase(true);
  79.                             }
  80.                         }
  81.                         foreach (ObjectId id in btr.IncludingErased)
  82.                         {
  83.                             using (DBObject dbo = id.Open(OpenMode.ForWrite, true, true))
  84.                             {
  85.                                 dbo.Erase(false);
  86.                             }
  87.                         }
  88.                     }
  89.                 }
  90.                 trx.Commit();
  91.             }
  92.         }
  93.  


TheMaster

  • Guest
Re: Witches, Ghosts, and Transactions
« Reply #5 on: April 11, 2013, 08:37:20 AM »
Hi Jeff, thanks for the testing.

Profiling is good for identifying bottlenecks by breakiing down execution time by method, but I haven't found it as useful for evaluating the net-sum difference between two different approaches to doing the same thing. Simple, informal timing seems to be more effective and less confusing.

I'm also not sure if manual timings should be considered valid when using the Profiler, but I have yet to see the OpenCloseTransaction do anything any faster than a regular transaction. Perhaps with write-operations it may be different but I've yet to do any testing.
« Last Edit: April 11, 2013, 08:41:31 AM by TT »

BlackBox

  • King Gator
  • Posts: 3770
Re: Witches, Ghosts, and Transactions
« Reply #6 on: April 11, 2013, 09:11:13 AM »
... I have yet to see the OpenCloseTransaction do anything any faster than a regular transaction. Perhaps with write-operations it may be different but I've yet to do any testing.

Forgive my asking... If there is no veritable speed difference, then what benefit is there to:

Quote from: ArxMgd.chm, OpenCloseTransaction Class
[OpenCloseTransaction] may be used instead of the transaction class in certain scenarios.. It wraps the ObjectId.Open/Close functions, but makes it easier to correctly pair these functions by storing references to every object opened and automatically closing them.


Is there any 'best practice' as to when one should use one Transaction over the other?

I vaguely recall you and either Kean, or Stephen discussing something about this, perhaps I should find and re-read that as well.

Cheers
"How we think determines what we do, and what we do determines what we get."

TheMaster

  • Guest
Re: Witches, Ghosts, and Transactions
« Reply #7 on: April 11, 2013, 09:54:59 AM »
... I have yet to see the OpenCloseTransaction do anything any faster than a regular transaction. Perhaps with write-operations it may be different but I've yet to do any testing.

Forgive my asking... If there is no veritable speed difference, then what benefit is there to:

Quote from: ArxMgd.chm, OpenCloseTransaction Class
[OpenCloseTransaction] may be used instead of the transaction class in certain scenarios.. It wraps the ObjectId.Open/Close functions, but makes it easier to correctly pair these functions by storing references to every object opened and automatically closing them.

The OpenCloseTransaction is just a List<DBObject>, which calls each element's Close() method when the transaction is committed, or calls each element's Cancel() method if the transaction is aborted. The reason for its existence is because dealing directly with the Open()/Close()/Dispose() mechanism is tricky and error-prone, and there are some cases where a regular transaction cannot be used to open objects, mainly from the handlers of many events, especially lower-level ones. If you use a regular transaction in those 'certain senarios', it can very likely cause problems with UNDO/REDO, because a regular transaction actually behaves like an UNDO Group. For example, aborting a regular transaction will roll back more than just the changes made to objects that were opened with the transaction. It essentially rolls back everything, including changes to system variables, and so forth.

Quote

Is there any 'best practice' as to when one should use one Transaction over the other?

I vaguely recall you and either Kean, or Stephen discussing something about this, perhaps I should find and re-read that as well.

Cheers

Use regular transactions when you're writing custom commands that modify the database, and want everything rolled back if the transaction is aborted. Use OpenCloseTransaction when you are opening objects in event handlers, Overrule overrides, or other code that's not being called from a registered handler for a command.

The sticky wicket for me all along with OpenCloseTransaction is that I have reusable helper APIs that are called from both contexts (e.g., from custom command handlers and from event handlers), and that makes things much more complicated because you have to use a different kind of transaction depending on the context that your code is being used in.
« Last Edit: April 11, 2013, 10:36:46 AM by TT »

TheMaster

  • Guest
Re: Witches, Ghosts, and Transactions
« Reply #8 on: April 11, 2013, 10:34:40 AM »
It appears that my testing methodolgy is flawed.

The flaw, is that the test code creates the objects and in the same session, runs the tests.

If the objects are created, saved to a DWG file, the session is closed, and in a new session, the DWG file is loaded, and the tests run, the results are different, and OpenCloseTransaction has a more signficiant edge, but still not enough to sway me. There may also be a difference when doing more than just opening objects (I changed to code to open the objects for write and change each entity's ColorIndex).

Code - Text: [Select]
  1.  
  2. Command: TT1
  3. AcTrTransaction: 1370
  4.  
  5. Command: TT2
  6. OpenCloseTransaction: 1186
  7.  
  8. Command: TT1
  9. AcTrTransaction: 1355
  10.  
  11. Command: TT2
  12. OpenCloseTransaction: 1169
  13.  
  14. Command: TT1
  15. AcTrTransaction: 1354
  16.  
  17. Command: TT2
  18. OpenCloseTransaction: 1159
  19.  
  20.  

BlackBox

  • King Gator
  • Posts: 3770
Re: Witches, Ghosts, and Transactions
« Reply #9 on: April 11, 2013, 10:37:26 AM »
Many thanks for taking the time to entertain my curiosity about Transactions, Tony... I'll definitely be referring to that post moving forward.

Cheers
"How we think determines what we do, and what we do determines what we get."

TheMaster

  • Guest
Re: Witches, Ghosts, and Transactions
« Reply #10 on: April 11, 2013, 12:04:21 PM »
Many thanks for taking the time to entertain my curiosity about Transactions, Tony... I'll definitely be referring to that post moving forward.

Cheers

Thanks.  I can't help but wonder what point there is to testing with so much data that anything else I try to do with AutoCAD results in it locking up and having to be killed. If the volume of data needed to see a difference of 1/10 of a second is enough to bring down the house, then I'm just wasting my time.

Of course, there's always the cloud :roll:

Jeff H

  • Needs a day job
  • Posts: 6150
Re: Witches, Ghosts, and Transactions
« Reply #11 on: April 13, 2013, 03:51:55 AM »
Hi Tony,
 
This is not any news to you as I have seen you mention this and cache the TransactionManager property of Transaction class, but doing so does change the results.
At bottom of post for anyone interested I added code to show how caching TransactionManager aligns functions calls similar to OpenCloseTrasaction.
 
For a small number of objects could not get them to register any time, but looks like OpenCloseTransaction on my system will give you whopping 1ms edge(500 objects) before a regular transaction begins to catch up(1000 objects)
As the number of objects increases the Transaction starts biatch slapping the OpenCloseTransaction where no timer is needed to tell the transaction performs much better.
 
Here are the only changes I made to original code which was changing "tr.GetObject" to "acTm .GetObject"
Code - C#: [Select]
  1. using AcApTransactionManager = Autodesk.AutoCAD.ApplicationServices.TransactionManager;
  2. //////
  3. ////....
  4. /////
  5.         [CommandMethod("TT1")]
  6.         public static void TestAcTrTransaction()
  7.         {
  8.             Document doc = Application.DocumentManager.MdiActiveDocument;
  9.             Database db = doc.Database;
  10.             Editor ed = doc.Editor;
  11.             AcApTransactionManager acTm = doc.TransactionManager;
  12.             using (Transaction tr = acTm.StartTransaction())
  13.             {
  14.                 BlockTableRecord btr = (BlockTableRecord)
  15.                    acTm.GetObject(db.CurrentSpaceId, OpenMode.ForRead);
  16.                 RXClass dbPointClass = RXClass.GetClass(typeof(DBPoint));
  17.                 int i = 0;
  18.                 Stopwatch sw = Stopwatch.StartNew();
  19.                 foreach (ObjectId id in btr)
  20.                 {
  21.                     if (id.ObjectClass == dbPointClass)
  22.                     {
  23.                         DBPoint p = (DBPoint)acTm.GetObject(id, OpenMode.ForRead);
  24.                         ++i;
  25.                     }
  26.                 }
  27.                 sw.Stop();
  28.                 ed.WriteMessage("\nAcTrTransaction: {0}", sw.ElapsedMilliseconds);
  29.                 tr.Commit();
  30.             }
  31.         }
  32.  
Here are the results and these were consistently producing same results.
Code - Text: [Select]
  1. Command: RNDPOINTS
  2. Number of points: 100
  3. Command: TT1
  4. AcTrTransaction: 0
  5. Command: TT2
  6. OpenCloseTransaction: 0
  7.  
  8. Command: RNDPOINTS
  9. Number of points: 500
  10. Command: TT1
  11. AcTrTransaction: 1
  12. Command: TT2
  13. OpenCloseTransaction: 0
  14.  
  15. Command: RNDPOINTS
  16. Number of points: 1000
  17. Command: TT1
  18. AcTrTransaction: 2
  19. Command: TT2
  20. OpenCloseTransaction: 2
  21.  
  22. Command: RNDPOINTS
  23. Number of points: 100000
  24. Command: TT1
  25. AcTrTransaction: 209
  26. Command: TT2
  27. OpenCloseTransaction: 260
  28. Command: TT1
  29. AcTrTransaction: 210
  30. Command: TT2
  31. OpenCloseTransaction: 259
  32.  
  33. Command: RNDPOINTS
  34. Number of points: 500000
  35. Command: TT1
  36. AcTrTransaction: 1111
  37. Command: TT2
  38. OpenCloseTransaction: 2298
  39. Command: TT1
  40. AcTrTransaction: 1094
  41. Command: TT2
  42. OpenCloseTransaction: 2285
  43.  

 
To see why here is a OpenCloseTransaction
Code - C#: [Select]
  1. public override unsafe DBObject GetObject(ObjectId id, OpenMode mode, [MarshalAs(UnmanagedType.U1)] bool openErased, [MarshalAs(UnmanagedType.U1)] bool forceOpenOnLockedLayer)
  2. {
  3.     AcDbObject* objPtr;
  4.     int num = AcMgOpenCloseTransaction.getObject(this.GetImpObj(), &objPtr, (AcDbObjectId) ((long) &id), (AcDb.OpenMode) mode, openErased, forceOpenOnLockedLayer);
  5.     if (num != 0)
  6.     {
  7.         throw new Exception((ErrorStatus) num);
  8.     }
  9.     return (DBObject) RXObject.Create((IntPtr) objPtr, false);
  10. }
  11.  
With a transaction notice how in GetObject it makes a call to CheckTopTransaction() then uses its TransactionManager property which creates new wrapper to call GetObjectInternal() that is more comparable to OpenCloseTransaction GetObject().
I am not sure why they create a new wrapper for each use of the TransactionManager property.
Code - C#: [Select]
  1. public virtual unsafe DBObject GetObject(ObjectId id, OpenMode mode)
  2. {
  3.     this.CheckTopTransaction();
  4.     return TransactionManager.GetObjectInternal(AcDbImpTransaction.transactionManager((AcDbImpTransaction* modopt(IsConst) modopt(IsConst)) this.GetImpObj()), id, mode, false, false);
  5. }
  6.  
  7. public virtual TransactionManager TransactionManager
  8. {
  9.     get
  10.     {
  11.         IntPtr unmanagedPointer = new IntPtr(AcDbImpTransaction.transactionManager((AcDbImpTransaction* modopt(IsConst) modopt(IsConst)) this.GetImpObj()));
  12.         return (TransactionManager) RXObject.Create(unmanagedPointer, false);
  13.     }
  14. }
  15. internal static unsafe DBObject GetObjectInternal(AcDbTransactionManager* pTM, ObjectId id, OpenMode mode, [MarshalAs(UnmanagedType.U1)] bool openErased, [MarshalAs(UnmanagedType.U1)] bool forceOpenOnLockedLayer)
  16. {
  17.     AcDbObject* objPtr;
  18.     int num = GetObjectInTransaction(&objPtr, pTM, (AcDbObjectId) ((long) &id), (AcDb.OpenMode) mode, openErased, forceOpenOnLockedLayer);
  19.     if (num != 0)
  20.     {
  21.         throw new Exception((ErrorStatus) num);
  22.     }
  23.     IntPtr unmanagedPointer = new IntPtr((void*) objPtr);
  24.     return (DBObject) RXObject.Create(unmanagedPointer, false);
  25. }
  26.  
I never really understood the different results for large number of objects but it would now make sense if it was caused from GC from all new wrappers created.
 

TheMaster

  • Guest
Re: Witches, Ghosts, and Transactions
« Reply #12 on: April 17, 2013, 01:28:09 AM »
Hi Tony,
 
This is not any news to you as I have seen you mention this and cache the TransactionManager property of Transaction class, but doing so does change the results.
At bottom of post for anyone interested I added code to show how caching TransactionManager aligns functions calls similar to OpenCloseTrasaction.
 
For a small number of objects could not get them to register any time, but looks like OpenCloseTransaction on my system will give you whopping 1ms edge(500 objects) before a regular transaction begins to catch up(1000 objects)
As the number of objects increases the Transaction starts biatch slapping the OpenCloseTransaction where no timer is needed to tell the transaction performs much better.
 
Here are the only changes I made to original code which was changing "tr.GetObject" to "acTm .GetObject"
Code - C#: [Select]
  1. using AcApTransactionManager = Autodesk.AutoCAD.ApplicationServices.TransactionManager;
  2. //////
  3. ////....
  4. /////
  5.         [CommandMethod("TT1")]
  6.         public static void TestAcTrTransaction()
  7.         {
  8.             Document doc = Application.DocumentManager.MdiActiveDocument;
  9.             Database db = doc.Database;
  10.             Editor ed = doc.Editor;
  11.             AcApTransactionManager acTm = doc.TransactionManager;
  12.             using (Transaction tr = acTm.StartTransaction())
  13.             {
  14.                 BlockTableRecord btr = (BlockTableRecord)
  15.                    acTm.GetObject(db.CurrentSpaceId, OpenMode.ForRead);
  16.                 RXClass dbPointClass = RXClass.GetClass(typeof(DBPoint));
  17.                 int i = 0;
  18.                 Stopwatch sw = Stopwatch.StartNew();
  19.                 foreach (ObjectId id in btr)
  20.                 {
  21.                     if (id.ObjectClass == dbPointClass)
  22.                     {
  23.                         DBPoint p = (DBPoint)acTm.GetObject(id, OpenMode.ForRead);
  24.                         ++i;
  25.                     }
  26.                 }
  27.                 sw.Stop();
  28.                 ed.WriteMessage("\nAcTrTransaction: {0}", sw.ElapsedMilliseconds);
  29.                 tr.Commit();
  30.             }
  31.         }
  32.  
Here are the results and these were consistently producing same results.
Code - Text: [Select]
  1. Command: RNDPOINTS
  2. Number of points: 100
  3. Command: TT1
  4. AcTrTransaction: 0
  5. Command: TT2
  6. OpenCloseTransaction: 0
  7.  
  8. Command: RNDPOINTS
  9. Number of points: 500
  10. Command: TT1
  11. AcTrTransaction: 1
  12. Command: TT2
  13. OpenCloseTransaction: 0
  14.  
  15. Command: RNDPOINTS
  16. Number of points: 1000
  17. Command: TT1
  18. AcTrTransaction: 2
  19. Command: TT2
  20. OpenCloseTransaction: 2
  21.  
  22. Command: RNDPOINTS
  23. Number of points: 100000
  24. Command: TT1
  25. AcTrTransaction: 209
  26. Command: TT2
  27. OpenCloseTransaction: 260
  28. Command: TT1
  29. AcTrTransaction: 210
  30. Command: TT2
  31. OpenCloseTransaction: 259
  32.  
  33. Command: RNDPOINTS
  34. Number of points: 500000
  35. Command: TT1
  36. AcTrTransaction: 1111
  37. Command: TT2
  38. OpenCloseTransaction: 2298
  39. Command: TT1
  40. AcTrTransaction: 1094
  41. Command: TT2
  42. OpenCloseTransaction: 2285
  43.  

 
To see why here is a OpenCloseTransaction
Code - C#: [Select]
  1. public override unsafe DBObject GetObject(ObjectId id, OpenMode mode, [MarshalAs(UnmanagedType.U1)] bool openErased, [MarshalAs(UnmanagedType.U1)] bool forceOpenOnLockedLayer)
  2. {
  3.     AcDbObject* objPtr;
  4.     int num = AcMgOpenCloseTransaction.getObject(this.GetImpObj(), &objPtr, (AcDbObjectId) ((long) &id), (AcDb.OpenMode) mode, openErased, forceOpenOnLockedLayer);
  5.     if (num != 0)
  6.     {
  7.         throw new Exception((ErrorStatus) num);
  8.     }
  9.     return (DBObject) RXObject.Create((IntPtr) objPtr, false);
  10. }
  11.  
With a transaction notice how in GetObject it makes a call to CheckTopTransaction() then uses its TransactionManager property which creates new wrapper to call GetObjectInternal() that is more comparable to OpenCloseTransaction GetObject().
I am not sure why they create a new wrapper for each use of the TransactionManager property.
Code - C#: [Select]
  1. public virtual unsafe DBObject GetObject(ObjectId id, OpenMode mode)
  2. {
  3.     this.CheckTopTransaction();
  4.     return TransactionManager.GetObjectInternal(AcDbImpTransaction.transactionManager((AcDbImpTransaction* modopt(IsConst) modopt(IsConst)) this.GetImpObj()), id, mode, false, false);
  5. }
  6.  
  7. public virtual TransactionManager TransactionManager
  8. {
  9.     get
  10.     {
  11.         IntPtr unmanagedPointer = new IntPtr(AcDbImpTransaction.transactionManager((AcDbImpTransaction* modopt(IsConst) modopt(IsConst)) this.GetImpObj()));
  12.         return (TransactionManager) RXObject.Create(unmanagedPointer, false);
  13.     }
  14. }
  15. internal static unsafe DBObject GetObjectInternal(AcDbTransactionManager* pTM, ObjectId id, OpenMode mode, [MarshalAs(UnmanagedType.U1)] bool openErased, [MarshalAs(UnmanagedType.U1)] bool forceOpenOnLockedLayer)
  16. {
  17.     AcDbObject* objPtr;
  18.     int num = GetObjectInTransaction(&objPtr, pTM, (AcDbObjectId) ((long) &id), (AcDb.OpenMode) mode, openErased, forceOpenOnLockedLayer);
  19.     if (num != 0)
  20.     {
  21.         throw new Exception((ErrorStatus) num);
  22.     }
  23.     IntPtr unmanagedPointer = new IntPtr((void*) objPtr);
  24.     return (DBObject) RXObject.Create(unmanagedPointer, false);
  25. }
  26.  
I never really understood the different results for large number of objects but it would now make sense if it was caused from GC from all new wrappers created.

Thanks Jeff.

I'm still wondering why the implementation (of Transaction.GetObject() and ObjectId.GetObject()) are implemented in managed code, verses doing everything in native code (e.g., call the TransactionManager's getObject() method via objectId->database()->transactionManager(), rather than having to create all those managed wrappers for each native type and go through managed code.

It makes absolutely no sense at all, and is clearly a performance bottleneck if I've ever seen one.

MexicanCustard

  • Swamp Rat
  • Posts: 705
Re: Witches, Ghosts, and Transactions
« Reply #13 on: April 17, 2013, 08:42:23 AM »
Tony and Jeff,

So does that mean you guys are going to start using:
    TransactionManager.GetObject

Instead of the old standard:
    var tr=TransactionManager.StartTransaction
    tr.GetObject
Revit 2019, AMEP 2019 64bit Win 10

TheMaster

  • Guest
Re: Witches, Ghosts, and Transactions
« Reply #14 on: April 17, 2013, 10:05:07 AM »
Tony and Jeff,

So does that mean you guys are going to start using:
    TransactionManager.GetObject

Instead of the old standard:
    var tr=TransactionManager.StartTransaction
    tr.GetObject

In my case, no.  Because my methods take a Transaction as an optional argument for cases where they have to be used with OpenCloseTransaction (in event handlers for example), so I can't use TransactionManager in those cases, because sometimes the transaction will be a regular transaction, and other times it will be an OpenCloseTransaction.

In APIs that don't need to be called from event handlers, and never need to use OpenCloseTransaction, you can use the TransactionManager instead of Transaction, which has a slight performance edge.

Nit-picking minute differences in performance isn't always in the best interest of reusability, and in real world use, the difference may not be perceptible, so I usually prefer the more flexible option, even if it's marginally slower.

The important thing to remember about performance evaluation, is that if you have to use massive amounts of data to see any noticeable difference between two implementations or algorithms, it's not uncommon for virtual memory faults to end up becoming the effective-bottleneck, rather than the slower implementation.