perf: optimize C# loader to match legacy load times (#3268)#3270
perf: optimize C# loader to match legacy load times (#3268)#3270romangolev merged 11 commits intodevelopfrom
Conversation
- Cache PyRevitConfig.Load() as a static singleton with session-scoped invalidation - Cache Roslyn MetadataReference list and assembly build fingerprint as statics - Replace per-extension AppDomain.GetAssemblies() scans with pre-built HashSet lookup - Materialize libraryExtensions.ToList() once before the command loop - Move hook registration into C# SessionManagerService to eliminate redundant Python-side extension re-parse - Remove icon pre-loading step (File.ReadAllBytes on every icon) that legacy loader never had - Limit ReadPythonScriptConstants to first ~50 lines per script file - Auto-create empty pyRevit_config.ini if missing so Python configparser can save settings - Wire PyRevitConfig.ClearCache() into ExtensionParser.ClearAllCaches() for proper reload invalidation Before: 14.2s (C#) vs 12.7s (Legacy) — 11% slower After: 13.2s (C#) vs 13.3s (Legacy) — on par or faster
|
Unable to trigger custom agent "Code Reviewer". You have run out of credits 😔 |
|
@Wurschdhaud feel free to use the DLLs in the PR for your testing. |
There was a problem hiding this comment.
Pull request overview
This PR targets pyRevit’s C# Roslyn-based loader performance regressions (vs. the legacy Python loader) by removing redundant work in the session load pipeline and introducing several session-scoped caches.
Changes:
- Removes the Python-side “second pass” extension re-parse by moving hook registration into the C#
SessionManagerService. - Adds session-scoped caching for config loading, Roslyn metadata references, assembly fingerprinting, and loaded-assembly detection.
- Removes icon file pre-reading and reduces per-command allocations in command type generation.
Reviewed changes
Copilot reviewed 11 out of 27 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| pyrevitlib/pyrevit/loader/sessionmgr.py | Removes Python-side hook registration loop for the C# session path (now handled in C#). |
| dev/pyRevitLoader/pyRevitExtensionParser/PyRevitConfig.cs | Adds a cached default config instance with ClearCache() and ensures missing ini is created. |
| dev/pyRevitLoader/pyRevitExtensionParser/ExtensionParser.cs | Routes config caching through PyRevitConfig.Load(), clears config cache on reload, and exposes SanitizeClassName() for hook ID generation. |
| dev/pyRevitLoader/pyRevitAssemblyBuilder/UIManager/UIManagerService.cs | Removes icon pre-loading step to match legacy behavior and reduce I/O. |
| dev/pyRevitLoader/pyRevitAssemblyBuilder/UIManager/SessionManagerService.cs | Adds a dedicated hook-registration pass in the C# loader pipeline. |
| dev/pyRevitLoader/pyRevitAssemblyBuilder/UIManager/IconsHandling/IIconManager.cs | Removes PreloadExtensionIcons() from the icon manager interface. |
| dev/pyRevitLoader/pyRevitAssemblyBuilder/UIManager/IconsHandling/IconManager.cs | Removes the icon pre-read implementation. |
| dev/pyRevitLoader/pyRevitAssemblyBuilder/SessionManager/IHookManager.cs | Expands hook registration API to accept runtime assembly + search paths inputs. |
| dev/pyRevitLoader/pyRevitAssemblyBuilder/SessionManager/HookManager.cs | Implements C# hook discovery + EventHooks.RegisterHook() reflection calls. |
| dev/pyRevitLoader/pyRevitAssemblyBuilder/AssemblyMaker/CommandTypeGenerator.cs | Materializes library extension directories once per extension to avoid repeated ToList() allocations. |
| dev/pyRevitLoader/pyRevitAssemblyBuilder/AssemblyMaker/AssemblyBuilderService.cs | Caches loaded pyRevit assembly names and Roslyn metadata references; caches build fingerprint. |
| bin/netfx/engines/IPY342/pyRevitExtensionParser.dll | Updates the packaged parser binary for this engine folder. |
dev/pyRevitLoader/pyRevitAssemblyBuilder/SessionManager/HookManager.cs
Outdated
Show resolved
Hide resolved
dev/pyRevitLoader/pyRevitAssemblyBuilder/AssemblyMaker/AssemblyBuilderService.cs
Outdated
Show resolved
Hide resolved
dev/pyRevitLoader/pyRevitAssemblyBuilder/UIManager/SessionManagerService.cs
Outdated
Show resolved
Hide resolved
|
@jmcouffin another copilot run will be helpful. updated and pushed a new commit. |
…tion Agent-Logs-Url: https://github.com/pyrevitlabs/pyRevit/sessions/31c0aab5-bcb5-48f7-bf0a-5f09cfb4ae3a Co-authored-by: jmcouffin <7872003+jmcouffin@users.noreply.github.com>
…-checked locking race Agent-Logs-Url: https://github.com/pyrevitlabs/pyRevit/sessions/f0e0af65-14ac-45fa-8cdd-d0419b192b94 Co-authored-by: jmcouffin <7872003+jmcouffin@users.noreply.github.com>
- Introduced TryParseHookFileName method to encapsulate hook file name parsing logic. - Updated CreateHookId and BuildHookSearchPaths methods to internal access modifier for better encapsulation. - Added HookManagerTests.cs to the project for improved test coverage.
…abs/pyRevit into tay0thman-fix-perf-3268
|
@romangolev Please, we need your expert look here |
|
@tay0thman @jmcouffin this would be an awesome addition to the loader. Great job with hooks and caching 💪 |
|
📦 New work-in-progress (wip) builds are available for 6.3.0.26097+2314-wip |

<remarks>forPyRevitConfig.Load()to accurately reflect that when loading from the default path, the method now eagerly creates the config file (and its parent directory) if missing — and that custom-path calls are uncached and never create filesPyRevitConfig: mark_defaultInstanceasvolatile(required for correct double-checked locking in C#) and hold_cacheLockinClearCache()to prevent a race with the write insideLoad()