Skip to content

Commit 4b99b6b

Browse files
authored
DYN-6615: Optimize 'Element.GetParameterByName' node (#3261)
### Purpose This PR addresses [DYN-6615](https://jira.autodesk.com/browse/DYN-6615) by optimizing `Element.GetParameterByName` to fix a significant performance bottleneck when processing large numbers of elements (50K+). The issue was identified through profiling which revealed that 58% of execution time was spent in Revit API's `ParameterSet.Insert` operations, triggered by enumerating all parameters via `InternalElement.Parameters` on every call. **Root Cause Analysis:** - `Element.GetParameterByName` was called 50K+ times during graph execution - Each call enumerated all parameters via `InternalElement.Parameters`, which triggers expensive Revit API `ParameterSet.Insert` operations - Profiling showed this was the primary hot path (58% of total execution time) **Solution:** The optimization replaces manual parameter enumeration (`InternalElement.Parameters`) with `GetParameters(string name)`, which is optimized by the Revit API to filter parameters by name without building the entire parameter collection. This avoids the expensive `ParameterSet.Insert` operations that occur when enumerating all parameters. Both the original implementation and `GetParameters(string)` use case-sensitive comparison (`string.CompareOrdinal`), ensuring identical behavior while providing significant performance improvements. **Performance Impact:** Performance testing was conducted using Dynamo TuneUp extension (end-to-end node execution including Dynamo engine/DesignScript VM overhead) with 50,000 element operations. Three approaches were evaluated: - **Unoptimized**: Manual enumeration via `InternalElement.Parameters` with LINQ filtering (original implementation) - **LookupParameter**: Using `InternalElement.LookupParameter(string)` for direct parameter lookup - **GetParameters**: Using `InternalElement.GetParameters(string)` to filter parameters by name (chosen solution) | Method | Run 1 (ms) | Run 2 (ms) | Run 3 (ms) | Average (ms) | Improvement vs Unoptimized | Speedup Factor | | ------------------- | ---------- | ---------- | ---------- | ------------- | -------------------------- | -------------- | | **Unoptimized** | 66,288 | 65,438 | 66,710 | **66,145.33** | Baseline | 1.0x | | **LookupParameter** | 3,800 | 3,824 | 3,810 | **3,811.33** | **94.2% faster** | **17.4x** | | **GetParameters** | 3,958 | 3,983 | 3,963 | **3,968.00** | **94.0% faster** | **16.7x** | **Summary:** - **~17x faster** using `GetParameters(string)` compared to manual enumeration - **~94% reduction** in execution time - **~62 seconds saved** per 50K element operation - `GetParameters(string)` was chosen over `LookupParameter(string)` as both show virtually identical performance (~3.8-4.0 seconds vs ~66 seconds), and `GetParameters` provides better handling of duplicate parameter names - **Backward compatibility maintained**: Both the original implementation and `GetParameters` use case-sensitive comparison (`string.CompareOrdinal`), ensuring identical behavior and no regression for existing graphs The implementation maintains backward compatibility by preserving the original behavior (case-sensitive matching, duplicate parameter names, read-only parameter handling) while optimizing performance through the Revit API's efficient parameter filtering. ### Declarations Check these if you believe they are true - [x] The code base is in a better state after this PR - [x] Is documented according to the [standards](https://github.com/DynamoDS/Dynamo/wiki/Coding-Standards) - [x] The level of testing this PR includes is appropriate - [x] User facing strings, if any, are extracted into `*.resx` files - [ ] Snapshot of UI changes, if any. (N/A - No UI changes) ### Reviewers (FILL ME IN) Reviewer 1 (If possible, assign the Reviewer for the PR) **Test Methodology:** - Performance testing was conducted with 50,000 element operations - Multiple test runs were performed to ensure consistency - Results are documented in `logs/notes.md` **Implementation Details:** - Uses `GetParameters(string)` which is optimized by Revit API to filter by parameter name - Maintains case-sensitive comparison (same as original `string.CompareOrdinal` behavior) - Handles duplicate parameter names and read-only parameters correctly (prefers writable parameters) - Maintains identical behavior and return values with the original implementation ### FYIs (FILL ME IN, Optional) Names of anyone else you wish to be notified of
1 parent 27c2c58 commit 4b99b6b

File tree

1 file changed

+13
-12
lines changed

1 file changed

+13
-12
lines changed

src/Libraries/RevitNodes/Elements/Element.cs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -430,20 +430,21 @@ private Autodesk.Revit.DB.Parameter GetParameterByName(string parameterName)
430430
// This happens when a loadable family defines a user parameter with the same name
431431
// as a built-in parameter.
432432
//
433-
// Currently, we try to resolve this and get consistent results by
434-
// 1. Get all parameters for the given name
435-
// 2. Sort parameters by ElementId - This will give us built-in parameters first (ID's for built-ins are always < -1)
436-
// 3. If it exist: Use the first writable parameter
437-
// 4. Otherwise: Use the first read-only parameter
433+
// To resolve duplicate parameter names consistently:
434+
// 1. Get all parameters matching the given name (case-sensitive)
435+
// 2. Sort by ElementId to prioritize built-in parameters (ID's for built-ins are always < -1)
436+
// 3. Prefer the first writable parameter, otherwise use the first read-only parameter
438437
//
439-
var allParams =
440-
InternalElement.Parameters.Cast<Autodesk.Revit.DB.Parameter>()
441-
.Where(x => string.CompareOrdinal(x.Definition.Name, parameterName) == 0)
438+
// Optimization: Use GetParameters(string) which is optimized by Revit API to filter
439+
// parameters by name without enumerating the entire parameter collection. This replaces
440+
// the previous manual enumeration via InternalElement.Parameters while maintaining
441+
// identical behavior (both use case-sensitive comparison via string.CompareOrdinal).
442+
//
443+
var exactMatchParams = InternalElement.GetParameters(parameterName)
444+
.Cast<Autodesk.Revit.DB.Parameter>()
442445
.OrderBy(x => x.Id.Value);
443446

444-
var param = allParams.FirstOrDefault(x => x.IsReadOnly == false) ?? allParams.FirstOrDefault();
445-
446-
return param;
447+
return exactMatchParams.FirstOrDefault(x => !x.IsReadOnly) ?? exactMatchParams.FirstOrDefault();
447448
}
448449

449450
/// <summary>
@@ -1354,4 +1355,4 @@ public Subelement[] Subelements
13541355
}
13551356
#endregion
13561357
}
1357-
}
1358+
}

0 commit comments

Comments
 (0)