I read somewhere recently in a post that using LINQ is fast because it does not open objects through a transaction but looks at the properties of the object. To explore this, I created 2 new versions of the GetLabelInserts() function which are identical from the outside. One differs from Kean's method by using the BlockReference.IsDynamicBlock and .DynamicBlockTableRecord properties rather than iterating through xdata, the other uses the AcDbExtensions.GetObjects<>() that I found on the mentioned post.
In summary:
Kean's method is fastest with less than about 300 blocks to query
My modification is slightly faster on large datasets (check out the last result.. 1000001 blocks!)
LINQ is much slower than I expected... Can anybody out there show me something I've done wrong to make the LINQ expression run so slow?
Also, can anyone comment further on what is really happening behind the scenes to create these results?
Thanks!
[CommandMethod("TestDynamic")]
public void getDynamicBlocks()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
PromptEntityOptions peo
= new PromptEntityOptions
("\nSelect Block"); peo.SetRejectMessage("\nNot a BlockReference!");
peo
.AddAllowedClass(typeof(BlockReference
),
true); PromptEntityResult per = ed.GetEntity(peo);
if (per.Status != PromptStatus.OK)
return;
string BlockName;
using (Transaction tr = doc.TransactionManager.StartTransaction())
{
BlockReference br = (BlockReference)tr.GetObject(per.ObjectId, OpenMode.ForRead);
BlockTableRecord testblockdef = (BlockTableRecord)tr.GetObject(br.DynamicBlockTableRecord, OpenMode.ForRead);
BlockName = testblockdef.Name;
}
Stopwatch timer
= new Stopwatch
(); timer.Start();
List<BlockReference> dynamicblocks = GetDynamicLabelInserts(doc, db, ed, BlockName);
timer.Stop();
ed.WriteMessage("\nTook {0} ({1} ticks) to get {2} blocks ({3} ticks/block) using LINQ method", timer.Elapsed, timer.ElapsedTicks, dynamicblocks.Count, timer.ElapsedTicks / dynamicblocks.Count);
timer.Reset();
timer.Start();
List<BlockReference> mydynamicblocks = MyGetDynamicLabelInserts(doc, db, ed, BlockName);
timer.Stop();
ed.WriteMessage("\nTook {0} ({1} ticks) to get {2} blocks ({3} ticks/block) using my method", timer.Elapsed, timer.ElapsedTicks, mydynamicblocks.Count, timer.ElapsedTicks/ mydynamicblocks.Count);
timer.Reset();
timer.Start();
List<BlockReference> xdatablocks = GetLabelInserts(doc, db, ed, BlockName);
timer.Stop();
ed.WriteMessage("\nTook {0} ({1} ticks) to get {2} blocks ({3} ticks/block) using Kean's method", timer.Elapsed, timer.ElapsedTicks, xdatablocks.Count, timer.ElapsedTicks/ xdatablocks.Count);
timer.Reset();// <---- Fixed... Thanks Jeff!
timer.Start();
List<BlockReference> dynamicblocks2 = GetDynamicLabelInserts(doc, db, ed, BlockName);
timer.Stop();
ed.WriteMessage("\nTook {0} ({1} ticks) to get {2} blocks ({3} ticks/block) using LINQ method", timer.Elapsed, timer.ElapsedTicks, dynamicblocks2.Count, timer.ElapsedTicks/ dynamicblocks2.Count);
timer.Reset();
timer.Start();
List<BlockReference> mydynamicblocks2 = MyGetDynamicLabelInserts(doc, db, ed, BlockName);
timer.Stop();
ed.WriteMessage("\nTook {0} ({1} ticks) to get {2} blocks ({3} ticks/block) using my method", timer.Elapsed, timer.ElapsedTicks, mydynamicblocks2.Count, timer.ElapsedTicks/ mydynamicblocks2.Count);
timer.Reset();
timer.Start();
List<BlockReference> xdatablocks2 = GetLabelInserts(doc, db, ed, BlockName);
timer.Stop();
ed.WriteMessage("\nTook {0} ({1} ticks) to get {2} blocks ({3} ticks/block) using Kean's method", timer.Elapsed, timer.ElapsedTicks, xdatablocks2.Count, timer.ElapsedTicks/ xdatablocks2.Count);
}
// LINQ method
public List<BlockReference> GetDynamicLabelInserts(Document doc, Database db, Editor ed, string BlockName)
{
List
<BlockReference
> labels
= new List
<BlockReference
>(); using (Transaction tr = doc.TransactionManager.StartTransaction())
{
BlockTable bt =
(BlockTable)tr.GetObject(
db.BlockTableId,
OpenMode.ForRead
);
//don't waste time if the block isn't in the drawing
if (!bt.Has(BlockName))
{
ed.WriteMessage("/nCannot find block {0}", BlockName);
return labels;
}
BlockTableRecord ps = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.PaperSpace], OpenMode.ForRead);
BlockTableRecord btr = (BlockTableRecord)tr.GetObject(bt[BlockName], OpenMode.ForRead);
Handle blkHandle = btr.Handle;
IEnumerable<BlockReference> potentials = ps.GetObjects<BlockReference>(OpenMode.ForRead).Where(BlockReference => BlockReference.Name == BlockName || BlockReference.IsDynamicBlock == true);
foreach (BlockReference block in potentials)
{
if (block.Name == BlockName)
{
labels.Add(block);
continue;
}
BlockTableRecord blockDef = (BlockTableRecord)tr.GetObject(block.DynamicBlockTableRecord, OpenMode.ForRead);
if (blockDef.Name == BlockName)
{
labels.Add(block);
continue;
}
}
tr.Commit();
}
return labels;
}
// My mod to Kean's method
public List<BlockReference> MyGetDynamicLabelInserts(Document doc, Database db, Editor ed, string BlockName)
{
List
<BlockReference
> labels
= new List
<BlockReference
>(); using (Transaction tr = doc.TransactionManager.StartTransaction())
{
BlockTable bt =
(BlockTable)tr.GetObject(
db.BlockTableId,
OpenMode.ForRead
);
//don't waste time if the block isn't in the drawing
if (!bt.Has(BlockName))
{
ed.WriteMessage("/nCannot find block {0}", BlockName);
return labels;
}
BlockTableRecord ps = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.PaperSpace], OpenMode.ForRead);
BlockTableRecord btr = (BlockTableRecord)tr.GetObject(bt[BlockName], OpenMode.ForRead);
Handle blkHandle = btr.Handle;
foreach (ObjectId id in ps)
{
Entity ent = tr.GetObject(id, OpenMode.ForRead) as Entity;
if (ent != null)
{
BlockReference br = ent as BlockReference;
if (br != null)
{
if (br.Name == BlockName)
{
labels.Add(br);
}
else
{
if (br.IsDynamicBlock == true)
{
BlockTableRecord btr2 = (BlockTableRecord)tr.GetObject(br.DynamicBlockTableRecord, OpenMode.ForRead);
if (btr2.Name == BlockName)
{
labels.Add(br);
}
}
}
}
}
}
tr.Commit();
}
return labels;
}
//Kean's method
public List<BlockReference> GetLabelInserts(Document doc, Database db, Editor ed, string BlockName)
{
List
<BlockReference
> labels
= new List
<BlockReference
>(); using (Transaction tr = doc.TransactionManager.StartTransaction())
{
BlockTable bt =
(BlockTable)tr.GetObject(
db.BlockTableId,
OpenMode.ForRead
);
//don't waste time if the block isn't in the drawing
if (!bt.Has(BlockName))
{
ed.WriteMessage("/nCannot find block {0}", BlockName);
return labels;
}
BlockTableRecord ps = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.PaperSpace], OpenMode.ForRead);
BlockTableRecord btr = (BlockTableRecord)tr.GetObject(bt[BlockName], OpenMode.ForRead);
Handle blkHandle = btr.Handle;
foreach (ObjectId id in ps)
{
Entity ent = tr.GetObject(id, OpenMode.ForRead) as Entity;
if (ent != null)
{
BlockReference br = ent as BlockReference;
if (br != null)
{
BlockTableRecord bd =
(BlockTableRecord)tr.GetObject(
br.BlockTableRecord,
OpenMode.ForRead
);
if (bd.Name == BlockName)
{
labels.Add(br);
//ed.WriteMessage("\n{0},{1}", br.Position, br.Rotation);
}
else
{
BlockTableRecord btr2 = (BlockTableRecord)tr.GetObject(br.BlockTableRecord, OpenMode.ForRead);
var xdata = btr2.XData;
if (xdata != null)
{
// Get the XData as an array of TypeValues and loop
// through it
var tvs = xdata.AsArray();
for (int i = 0; i < tvs.Length; i++)
{
// The first value should be the RegAppName
var tv = tvs[i];
if (
tv.TypeCode == (int)DxfCode.ExtendedDataRegAppName
)
{
// If it's the one we care about...
if ((string)tv.Value == "AcDbBlockRepBTag")
{
// ... then loop through until we find a
// handle matching our blocks or otherwise
// another RegAppName
for (int j = i + 1; j < tvs.Length; j++)
{
tv = tvs[j];
if (
tv.TypeCode ==
(int)DxfCode.ExtendedDataRegAppName
)
{
// If we have another RegAppName, then
// we'll break out of this for loop and
// let the outer loop have a chance to
// process this section
i = j - 1;
break;
}
if (
tv.TypeCode ==
(int)DxfCode.ExtendedDataHandle
)
{
// If we have a matching handle...
if ((string)tv.Value == blkHandle.ToString())
{
// ... then we can add the block's name
// to the list and break from both loops
// (which we do by setting the outer index
// to the end)
labels.Add(br);
//ed.WriteMessage("\n{0},{1}", br.Position, br.Rotation);
i = tvs.Length - 1;
break;
}
}
}
}
}
}
}
}
}
}
}
tr.Commit();
}
return labels;
}
Command: TESTDYNAMIC
Select Block:
Took 00:00:00.0007665 (2401 ticks) to get 1 blocks (2401 ticks/block) using LINQ method
Took 00:00:00.0007394 (2316 ticks) to get 1 blocks (2316 ticks/block) using my method
Took 00:00:00.0004492 (1407 ticks) to get 1 blocks (1407 ticks/block) using Kean's method
Took 00:00:00.0011471 (3593 ticks) to get 1 blocks (3593 ticks/block) using LINQ method CORRECTION: 2186 ticks/block
Took 00:00:00.0006363 (1993 ticks) to get 1 blocks (1993 ticks/block) using my method
Took 00:00:00.0004025 (1261 ticks) to get 1 blocks (1261 ticks/block) using Kean's method
_________________________________
Command: TESTDYNAMIC
Select Block:
Took 00:00:00.0009823 (3077 ticks) to get 18 blocks (170 ticks/block) using LINQ method
Took 00:00:00.0008387 (2627 ticks) to get 18 blocks (145 ticks/block) using my method
Took 00:00:00.0005635 (1765 ticks) to get 18 blocks (98 ticks/block) using Kean's method
Took 00:00:00.0014887 (4663 ticks) to get 18 blocks (259 ticks/block) using LINQ method CORRECTION: 161 ticks/block
Took 00:00:00.0007477 (2342 ticks) to get 18 blocks (130 ticks/block) using my method
Took 00:00:00.0005245 (1643 ticks) to get 18 blocks (91 ticks/block) using Kean's method
__________________________________
TESTDYNAMIC
Select Block:
Took 00:00:00.0041345 (12950 ticks) to get 343 blocks (37 ticks/block) using LINQ method
Took 00:00:00.0021371 (6694 ticks) to get 343 blocks (19 ticks/block) using my method
Took 00:00:00.0019890 (6230 ticks) to get 343 blocks (18 ticks/block) using Kean's method
Took 00:00:00.0050383 (15781 ticks) to get 343 blocks (46 ticks/block) using LINQ method CORRECTION: 28 ticks/block
Took 00:00:00.0018846 (5903 ticks) to get 343 blocks (17 ticks/block) using my method
Took 00:00:00.0018721 (5864 ticks) to get 343 blocks (17 ticks/block) using Kean's method
_________________________________
Command: TESTDYNAMIC
Select Block:
Took 00:00:00.0115527 (36185 ticks) to get 818 blocks (44 ticks/block) using LINQ method
Took 00:00:00.0053515 (16762 ticks) to get 818 blocks (20 ticks/block) using my method
Took 00:00:00.0060287 (18883 ticks) to get 818 blocks (23 ticks/block) using Kean's method
Took 00:00:00.0115052 (36036 ticks) to get 818 blocks (44 ticks/block) using LINQ method CORRECTION: 21 ticks/block
Took 00:00:00.0027623 (8652 ticks) to get 818 blocks (10 ticks/block) using my method
Took 00:00:00.0031467 (9856 ticks) to get 818 blocks (12 ticks/block) using Kean's method
________________________________
Command: TESTDYNAMIC
Select Block:
Took 00:00:00.3539372 (1108584 ticks) to get 48018 blocks (23 ticks/block) using LINQ method
Took 00:00:00.1649740 (516723 ticks) to get 48018 blocks (10 ticks/block) using my method
Took 00:00:00.2242304 (702323 ticks) to get 48018 blocks (14 ticks/block) using Kean's method
Took 00:00:00.5656552 (1771716 ticks) to get 48018 blocks (36 ticks/block) using LINQ method CORRECTION: 22 ticks/block
Took 00:00:00.1645034 (515249 ticks) to get 48018 blocks (10 ticks/block) using my method
Took 00:00:00.2006236 (628383 ticks) to get 48018 blocks (13 ticks/block) using Kean's method
_____________________________________________
Command: TESTDYNAMIC
Select Block:
Took 00:00:01.4510952 (4545045 ticks) to get 197419 blocks (23 ticks/block) using LINQ method
Took 00:00:00.6719497 (2104646 ticks) to get 197419 blocks (10 ticks/block) using my method
Took 00:00:00.8568614 (2683817 ticks) to get 197419 blocks (13 ticks/block) using Kean's method
Took 00:00:02.2484758 (7042559 ticks) to get 197419 blocks (35 ticks/block) using LINQ method CORRECTION: 22 ticks/block
Took 00:00:00.8021520 (2512459 ticks) to get 197419 blocks (12 ticks/block) using my method
Took 00:00:00.8420208 (2637334 ticks) to get 197419 blocks (13 ticks/block) using Kean's method
__________________________________________________________
Command: TESTDYNAMIC
Select Block:
Took 00:00:07.2938638 (22845461 ticks) to get 1000001 blocks (22 ticks/block) using LINQ method
Took 00:00:03.6015105 (11280464 ticks) to get 1000001 blocks (11 ticks/block) using my method
Took 00:00:04.8079522 (15059218 ticks) to get 1000001 blocks (15 ticks/block) using Kean's method
Took 00:00:11.7611789 (36837753 ticks) to get 1000001 blocks (36 ticks/block) using LINQ method CORRECTION: 21 ticks/block
Took 00:00:03.7182243 (11646029 ticks) to get 1000001 blocks (11 ticks/block) using my method
Took 00:00:04.9463706 (15492765 ticks) to get 1000001 blocks (15 ticks/block) using Kean's method
Cheers,
Will