Today, let's present a benchmark monitoring filtered element collector performance.
First, however, a quick note on a very useful Forge learning resource:
Forge Learning Resource
If you are new to Forge or want to dive in deeper, you can find a collection of very cool Forge training material at learnforge.autodesk.io, focusing specifically on BIM360 and design automation.
Here is the table of contents:
- Before you start coding
- Tools
- OAuth
- View your models
- Create a server
- Authenticate
- Upload file to OSS
- Translate the file
- Show on Viewer
- View BIM 360 & Fusion models
- Create a server
- Authorize
- List hubs & projects
- User information
- Show on Viewer
- Modify your models
- Create a server
- Basic app UI
- Prepare a plugin
- Define an activity
Filtered Element Collector Benchmark
Back to the Revit API, I recently reiterated the differences between slow, slower still and faster filtering.
In the end, the only way to tell whether your filter is performing well or not is to implement some benchmarking for it.
Jai Hari Hara Sudhan very commendably did so, documenting his progress and sharing his results in a series of comments on that post and in his API speed test screencast demonstrating the benchmark running live.
Here is a summary of our discussion and his final benchmarking code:
Question: The above content is very useful.
I am using three methods to filter and select the element:
- Using
FilterRule
method (filters in floor by floor / level) - Using
Factory
Method (filters in the projects) - Selection with interface (element by element)
Question 1. Which one is the best in performance (quick)? FilterRule
or Factory
method?
Question 2. In the FamilySelectionFilter method, is there any better or more performant method to select the elements?
Answer: Nobody can tell you beforehand how these different approaches will perform in your specific context.
I therefore suggest that you benchmark them yourself and let us know the result.
The Building Coder topic groups lists several benchmarking examples that you can look at to see how.
Response: I implemented a benchmark.
It is impossible to determine exact constant performance (time), because the results differ from run to run.
Please refer to my API speed test screencast.
Finally, I rank the different approaches as follows:
- `Factory` method (average time = 766.1 microseconds)
– 100% - `FilterRule` method (average time = 889.0 microseconds)
– 116% slower than `Factory` - `Linq2` method (average time = 983.5 microseconds)
– 128% slower than `Factory` - `Linq1` method (average time = 1173.3 microseconds)
– 153% slower than `Factory`
The code as follows:
[Transaction( TransactionMode.Manual )] public class Elec_test : IExternalCommand { public Result Execute( ExternalCommandData commandData, ref string message, ElementSet elements ) { UIApplication uiapp = commandData.Application; UIDocument uidoc = uiapp.ActiveUIDocument; Application app = uiapp.Application; Document doc = uidoc.Document; Selection sel = uidoc.Selection; TaskDialog.Show( "BuilDTecH Architects", "BuilDTecH Architects by Sudhan" ); InputData InputData = new InputData(); InputData.ShowDialog(); IList<object> data = InputData.Data; /////////////////////////////// Input Values double LeftOffset = Convert.ToDouble( data[1] ), RightOffset = Convert.ToDouble( data[2] ), TopOffset = Convert.ToDouble( data[3] ), BottomOffset = Convert.ToDouble( data[4] ), NearClipOffset = -Convert.ToDouble( data[5] ), FarClipOffset = Convert.ToDouble( data[6] ); string Section_Name = "Electrical GangBox", ElectricalEquipment = "Modular Gang Box"; //Input /////////////////////////////// Input Values if( data[0].Equals( InputData.Option.ByFloor ) ) { Timer floortimeLinq1 = new Timer(); floortimeLinq1.Start(); IEnumerable<Element> elems = Linq1( doc, BuiltInCategory.OST_ElectricalEquipment, ElectricalEquipment ); floortimeLinq1.Stop(); TaskDialog.Show( "time", "LINQ1 Method Time = " + floortimeLinq1.Duration.ToString() + " No. of Elements = " + elems.Count().ToString() ); elems = null; Timer floortimeLinq2 = new Timer(); floortimeLinq2.Start(); elems = Linq2( doc, BuiltInCategory.OST_ElectricalEquipment, ElectricalEquipment ); floortimeLinq2.Stop(); TaskDialog.Show( "time", "LINQ2 Method Time = " + floortimeLinq2.Duration.ToString() + " No. of Elements = " + elems.Count().ToString() ); elems = null; Timer floortimeFilterRule = new Timer(); floortimeFilterRule.Start(); elems = FilterRule( doc, // uidoc.ActiveView.Id, BuiltInCategory.OST_ElectricalEquipment, ElectricalEquipment ); floortimeFilterRule.Stop(); TaskDialog.Show( "time", "Filter Rule Method Time = " + floortimeFilterRule.Duration.ToString() + " No. of Elements = " + elems.Count().ToString() ); elems = null; Timer floortimeFactoryRule = new Timer(); floortimeFactoryRule.Start(); elems = Factory( doc, BuiltInCategory.OST_ElectricalEquipment, ElectricalEquipment ); floortimeFactoryRule.Stop(); TaskDialog.Show( "time", " Factory Rule Method Time = " + floortimeFactoryRule.Duration.ToString() + " No. of Elements = " + elems.Count().ToString() ); } else if( data[0].Equals( InputData.Option.BySingle ) ) { TaskDialog td = new TaskDialog( "Element By Element" ); td.Title = "Want to Continue"; td.MainInstruction = "Do you want to create a new section"; td.CommonButtons = TaskDialogCommonButtons.Yes | TaskDialogCommonButtons.No; td.DefaultButton = TaskDialogResult.Yes; bool next = true; while( next ) { ISelectionFilter selFilter = new FamilySelectionFilter( doc, BuiltInCategory.OST_ElectricalEquipment, ElectricalEquipment ); Reference refe = sel.PickObject( ObjectType.Element, selFilter, "Select Object" ); Element ele = doc.GetElement( refe ); Draw_Section.Draw( doc, ele, Section_Name, LeftOffset, RightOffset, TopOffset, BottomOffset, NearClipOffset, FarClipOffset ); TaskDialogResult tdRes = td.Show(); if( tdRes == TaskDialogResult.No ) { next = false; } } } else if( data[0].Equals( InputData.Option.ByProject ) ) { Timer floortimeFactoryRule = new Timer(); floortimeFactoryRule.Start(); IEnumerable<Element> elems = Factory( doc, BuiltInCategory.OST_ElectricalEquipment, ElectricalEquipment ); floortimeFactoryRule.Stop(); TaskDialog.Show( "time", " Factory Rule Method Time = " + floortimeFactoryRule.Duration.ToString() + " No. of Elements = " + elems.Count().ToString() ); foreach( Element ele in elems ) { Draw_Section.Draw( doc, ele, Section_Name, LeftOffset, RightOffset, TopOffset, BottomOffset, NearClipOffset, FarClipOffset ); } } return Result.Succeeded; } public class FamilySelectionFilter : ISelectionFilter { Document Doc; string FmlyName = ""; int BultCatId; public FamilySelectionFilter( Document doc, BuiltInCategory BuiltInCat, string familyTypeName ) { Doc = doc; FmlyName = familyTypeName; BultCatId = (int) BuiltInCat; } public bool AllowElement( Element elem ) { return elem.Category.Id.IntegerValue == BultCatId; } public bool AllowReference( Reference refer, XYZ point ) { Element e = Doc.GetElement( refer ); return e.get_Parameter( BuiltInParameter.ELEM_FAMILY_PARAM ) .AsValueString().Equals( FmlyName ); } } #region Retrieve named family type using either LINQ or a parameter filter static IEnumerable<Element> Linq1( Document doc, BuiltInCategory BultCat, string familyTypeName ) { return new FilteredElementCollector( doc ).OfCategory( BultCat ).OfClass( typeof( FamilyInstance ) ) .Cast<FamilyInstance>() .Where( x => x.Symbol.Family.Name.Equals( familyTypeName ) ); } static IEnumerable<Element> Linq2( Document doc, BuiltInCategory BultCat, string familyTypeName ) { return new FilteredElementCollector( doc ).OfClass( typeof( FamilyInstance ) ).Cast<FamilyInstance>() .Where( x => x.get_Parameter( BuiltInParameter.ELEM_FAMILY_PARAM ) .AsValueString() == familyTypeName ); } static IEnumerable<Element> FilterRule( Document doc, // ElementId ActiveViewId, BuiltInCategory BultCat, string familyTypeName ) { return new FilteredElementCollector( doc )//,ActiveViewId) .OfCategory( BultCat ) .OfClass( typeof( FamilyInstance ) ) .WherePasses( new ElementParameterFilter( new FilterStringRule( new ParameterValueProvider( new ElementId( BuiltInParameter.ELEM_FAMILY_PARAM ) ), new FilterStringEquals(), familyTypeName, true ) ) ); } static IEnumerable<Element> Factory( Document doc, BuiltInCategory BultCat, string familyTypeName ) { return new FilteredElementCollector( doc ) .OfCategory( BultCat ) .OfClass( typeof( FamilyInstance ) ) .WherePasses( new ElementParameterFilter( ParameterFilterRuleFactory.CreateEqualsRule( new ElementId( BuiltInParameter.ELEM_FAMILY_PARAM ), familyTypeName, true ) ) ); } #endregion // Retrieve named family symbols using either LINQ or a parameter filter #region Timer public class Timer { [DllImport( "Kernel32.dll" )] private static extern bool QueryPerformanceCounter( out long lpPerformanceCount ); [DllImport( "Kernel32.dll" )] private static extern bool QueryPerformanceFrequency( out long lpFrequency ); private long startTime, stopTime; private long freq; /// <summary> /// Constructor /// </summary> public Timer() { startTime = 0; stopTime = 0; if( !QueryPerformanceFrequency( out freq ) ) { throw new Win32Exception( "high-performance counter not supported" ); } } /// <summary> /// Start the timer /// </summary> public void Start() { Thread.Sleep( 0 ); // let waiting threads work QueryPerformanceCounter( out startTime ); } /// <summary> ///Stop the timer /// </summary> public void Stop() { QueryPerformanceCounter( out stopTime ); } /// <summary> /// Return the duration of the timer in seconds /// </summary> public double Duration { get { return (double) ( stopTime - startTime ) / (double) freq; } } } #endregion // Timer
Many thanks to Sudhan for implementing this benchmark and reporting these useful (and reassuring) results!
I created a complete Visual Studio solution and added the missing bits and pieces to test this live in the FilterBenchmark GitHub repository.
I hope that this encourages you to do some benchmarking as well and helps you optimise your own filtered element collectors.