by Fenton Webb
Leading on from Part 4, and staying with ‘Performance’ I wanted to take a look at some .NET code and show you how to access the DWG database in the fastest way.
Over the years, I’m sure some of you have noticed how I like to write my DWG Database .NET in a very similar way to any code I write in ObjectARX, I try to only use Open/Close() unless I absolutely need to use Transactions.
The reason for this comes from my own personal experience and preferences:
- Open/Close is faster than Transaction/StartTransaction
- Open/Close can be more easily used in Event/Reactor callbacks, Transaction/StartTransaction can, but you need to really think about what you are doing
- Open/Close is neater to write, in my opinion
That said, other members in my team, including the original author of our AutoCAD .NET API, prefer to use the Transaction model because it’s more Object Oriented.
Here’s some code which shows the differences between using Transaction/StartTransaction and Open/Close…
First the Transaction version, which I personally don’t normally like writing…
public void StartTransaction()
{
Database db = HostApplicationServices.WorkingDatabase;
ObjectId msId = SymbolUtilityServices.GetBlockModelSpaceId(db);
TransactionManager tm = db.TransactionManager;
using (Transaction trx = tm.StartTransaction())
{
BlockTableRecord btr = (BlockTableRecord)tm.GetObject(msId, OpenMode.ForRead);
foreach (ObjectId id in btr)
{
Entity ent = (Entity)tm.GetObject(id, OpenMode.ForWrite);
// do something
}
trx.Commit();
}
}
Now my preferred way, Open/Close – see how much nicer it is?
public void OpenClose()
{
Database db = HostApplicationServices.WorkingDatabase;
ObjectId msId = SymbolUtilityServices.GetBlockModelSpaceId(db);
using (BlockTableRecord btr = (BlockTableRecord)msId.Open(OpenMode.ForRead))
{
foreach (ObjectId id in btr)
{
using (Entity ent = (Entity)id.Open(OpenMode.ForWrite))
{
// do something
}
}
}
}
Now I have shown the differences between the two styles, I should explain that there is actually two more additional styles of coding DWG database handling inside of AutoCAD.NET; that is Transaction.StartOpenCloseTransaction() and ObjectId.GetObject().
The Transaction.StartOpenCloseTransaction() style is exactly the same as the normal Transaction.StartTransaction(), except that it wraps the Open/Close mechanism in a Transaction object. When I show the performance differences further down, you will understand the reasons to use it, but in the meantime, here’s a quick look at the code – the only difference between the ‘StartTransaction’ function above and this one is the call to StartOpenCloseTransaction() instead of StartTransaction()…
[CommandMethod("StartOpenCloseTransaction")]
public void StartOpenCloseTransaction()
{
Database db = HostApplicationServices.WorkingDatabase;
ObjectId msId = SymbolUtilityServices.GetBlockModelSpaceId(db);
TransactionManager tm = db.TransactionManager;
using (Transaction trx = tm.StartOpenCloseTransaction())
{
BlockTableRecord btr = (BlockTableRecord)tm.GetObject(msId, OpenMode.ForRead);
foreach (ObjectId id in btr)
{
Entity ent = (Entity)tm.GetObject(id, OpenMode.ForWrite);
// do something
}
trx.Commit();
}
}
The ObjectId.GetObject() style utilizes the normal Transaction object mechanism, but this way gives you a slightly difference access point.
Why would you use ObjectId.GetObject() instead of Transaction.GetObject() I hear some of you ask – basically, the ObjectId.GetObject() obtains the top most transaction in the transaction stack, so it can save time when coding (no need to pass the transaction around) – that said it can cost a few extra CPU cycles to use which you can see affecting the results below.
I have to confess that the sample code above was obtained from an original version that “Jeff H” of TheSwamp.org created as part of his own performance testing that he posted. I really liked what he did (and others that read the same post can relate to it also) so I thought I’d use it here for my own benchmarking – Thanks Jeff H!
His original code had some little bugs in it which he actually pointed out himself; his code opened some objects for read and then those same objects, in different test functions, for write which probably made each test function slightly unfair to the other. I corrected those issues, and removed the extra “false, false” parameters from the Open() and GetObject() see the bottom for the code…
I recommend that you only ever open for write when you absolutely need too, otherwise you may be costing yourself valuable CPU cycles.
Now here are the test results – AutoCAD 2013, 55,000 entities in Model Space, each command run 5 times to average out any of my computer’s normal running tasks interference…
So it seems that StartOpenCloseTransaction(), in this test, even beats Open/Close on performance. It also seems that both of the normal ‘Transaction’ models StartTransaction() and ObjectID.GetObject() are significantly slower, but not much real difference between the two (0.3 of a second).
About The Transaction object in AutoCAD... The Transaction model (StartTransaction()) was invented way back when for a specific reason - transacting multiple writes on the same objects(s) and allowing layered rollbacks of these multi-write transactions.
Here’s what I recommend: If you guys are using StartTransaction(), and you don’t need the multiple write feature I just mentioned in the above paragraph, simply change your StartTransaction() to StartOpenCloseTransaction()…
Here are the actual results from the Command line.
“StartTransaction”
Command: STARTTRANSACTION
StartTransaction
1286338
Command:
STARTTRANSACTION
StartTransaction
1301005
Command:
STARTTRANSACTION
StartTransaction
1206838
Command:
STARTTRANSACTION
StartTransaction
1243078
Command:
STARTTRANSACTION
StartTransaction
1231894
“OpenCloseTransaction”
Command: OPENCLOSETRANSACTION
OpenCloseTransaction
511723
Command:
OPENCLOSETRANSACTION
OpenCloseTransaction
494753
Command:
OPENCLOSETRANSACTION
OpenCloseTransaction
495462
Command:
OPENCLOSETRANSACTION
OpenCloseTransaction
509738
Command:
OPENCLOSETRANSACTION
OpenCloseTransaction
491686
“OpenClose”
Command: OPENCLOSE
OpenClose
568263
Command:
OPENCLOSE
OpenClose
558977
Command:
OPENCLOSE
OpenClose
562863
Command:
OPENCLOSE
OpenClose
570294
Command:
OPENCLOSE
OpenClose
549607
“IDGetObject”
Command: IDGETOBJECT
IdGetObject
1606375
Command:
IDGETOBJECT
IdGetObject
1562226
Command:
IDGETOBJECT
IdGetObject
1571498
Command:
IDGETOBJECT
IdGetObject
1562189
Command:
IDGETOBJECT
IdGetObject
1527295
Random ran = new Random();
Stopwatch sw = new Stopwatch();
// code taken from original version written by "Jeff H"
[CommandMethod("StartTransaction")]
public void StartTransaction()
{
int colorIndex = ran.Next(1, 256);
Database db = HostApplicationServices.WorkingDatabase;
ObjectId msId = SymbolUtilityServices.GetBlockModelSpaceId(db);
Autodesk.AutoCAD.DatabaseServices.TransactionManager tm = db.TransactionManager;
sw.Reset();
sw.Start();
using (Transaction trx = tm.StartTransaction())
{
BlockTableRecord btr = (BlockTableRecord)tm.GetObject(msId, OpenMode.ForRead);
foreach (ObjectId id in btr)
{
Entity ent = (Entity)tm.GetObject(id, OpenMode.ForWrite);
ent.ColorIndex = colorIndex;
}
trx.Commit();
}
sw.Stop();
Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nStartTransaction\n");
Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage(sw.ElapsedTicks.ToString());
}
[CommandMethod("OpenClosetransaction")]
public void OpenClosetransaction()
{
int colorIndex = ran.Next(1, 256);
Database db = HostApplicationServices.WorkingDatabase;
ObjectId msId = SymbolUtilityServices.GetBlockModelSpaceId(db);
Autodesk.AutoCAD.DatabaseServices.TransactionManager tm = db.TransactionManager;
sw.Reset();
sw.Start();
using (Transaction trx = tm.StartOpenCloseTransaction())
{
BlockTableRecord btr = (BlockTableRecord)trx.GetObject(msId, OpenMode.ForRead);
foreach (ObjectId id in btr)
{
Entity ent = (Entity)trx.GetObject(id, OpenMode.ForWrite);
ent.ColorIndex = colorIndex;
}
trx.Commit();
}
sw.Stop();
Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nOpenCloseTransaction\n");
Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage(sw.ElapsedTicks.ToString());
}
[CommandMethod("OpenClose")]
public void OpenClose()
{
int colorIndex = ran.Next(1, 256);
Database db = HostApplicationServices.WorkingDatabase;
ObjectId msId = SymbolUtilityServices.GetBlockModelSpaceId(db);
sw.Reset();
sw.Start();
BlockTableRecord btr = (BlockTableRecord)msId.Open(OpenMode.ForRead);
foreach (ObjectId id in btr)
{
Entity ent = (Entity)id.Open(OpenMode.ForWrite);
ent.ColorIndex = colorIndex;
ent.Close();
}
btr.Close();
sw.Stop();
Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nOpenClose\n");
Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage(sw.ElapsedTicks.ToString());
}
[CommandMethod("IdGetObject")]
public void IdGetObject()
{
int colorIndex = ran.Next(1, 256);
Database db = HostApplicationServices.WorkingDatabase;
ObjectId msId = SymbolUtilityServices.GetBlockModelSpaceId(db);
Autodesk.AutoCAD.DatabaseServices.TransactionManager tm = db.TransactionManager;
sw.Reset();
sw.Start();
using (Transaction trx = tm.StartTransaction())
{
BlockTableRecord btr = (BlockTableRecord)msId.GetObject(OpenMode.ForRead);
foreach (ObjectId id in btr)
{
Entity ent = (Entity)id.GetObject(OpenMode.ForWrite);
ent.ColorIndex = colorIndex;
}
trx.Commit();
}
sw.Stop();
Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nIdGetObject\n");
Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage(sw.ElapsedTicks.ToString());
}