From bd872a3f2e0cdb52fb3201997ddc19a84b52bf3e Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Sun, 12 Jul 2026 15:21:04 -0700 Subject: [PATCH] Cache normalized file names to avoid re-allocating them per cursor IsIncludedFileOrLocation runs for every cursor and re-derives the file's normalized name from scratch each time -- file.Name.ToString().NormalizePath(), plus a NormalizeFullPath() (Path.GetFullPath) on the traversal-name path. The same file recurs for every cursor it contains, so this repeats the same work thousands of times. Cache the normalized name and full name per CXFile, mirroring the existing _fileContents cache (same key type, cleared per translation unit in GenerateBindings). On the TerraFX Windows SDK d3d12 generation this drops sampled allocations from 179.6 MB to 142.7 MB (~20%), eliminating the 18.0 MB of NormalizePath and 8.7 MB of NormalizeFullPath entirely. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../PInvokeGenerator.Predicates.cs | 15 ++++++++++++--- .../PInvokeGenerator.cs | 3 +++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Predicates.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Predicates.cs index 7270c301..a73796ef 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Predicates.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Predicates.cs @@ -310,8 +310,17 @@ bool IsIncludedFileOrLocation(Cursor cursor, CXFile file, CXSourceLocation locat // Use case insensitive comparison on Windows var equalityComparer = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; - // Normalize paths to be '/' for comparison - var fileName = file.Name.ToString().NormalizePath(); + // Normalize paths to be '/' for comparison. The same file recurs for + // every cursor it contains, so cache the normalized names per file to + // avoid re-allocating them (and re-running Path.GetFullPath) each call. + if (!_fileNames.TryGetValue(file, out var names)) + { + var name = file.Name.ToString().NormalizePath(); + names = (name, name.NormalizeFullPath()); + _fileNames.Add(file, names); + } + + var fileName = names.Name; if (_visitedFiles.Add(fileName) && _config.LogVisitedFiles) { @@ -322,7 +331,7 @@ bool IsIncludedFileOrLocation(Cursor cursor, CXFile file, CXSourceLocation locat { return true; } - else if (_config.TraversalNames.Contains(fileName.NormalizeFullPath(), equalityComparer)) + else if (_config.TraversalNames.Contains(names.FullName, equalityComparer)) { return true; } diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs index 87bc0e58..08bdcb7d 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs @@ -63,6 +63,7 @@ public sealed partial class PInvokeGenerator : IDisposable private readonly Dictionary> _topLevelClassUsings; private readonly Dictionary> _topLevelClassAttributes; private readonly Dictionary _fileContents; + private readonly Dictionary _fileNames; private readonly HashSet _topLevelClassNames; private readonly HashSet _usedRemappings; private readonly string _placeholderMacroType; @@ -161,6 +162,7 @@ public PInvokeGenerator(PInvokeGeneratorConfiguration config, Func(StringComparer.Ordinal); _topLevelClassAttributes = new Dictionary>(StringComparer.Ordinal); _fileContents = []; + _fileNames = []; _topLevelClassUsings = new Dictionary>(StringComparer.Ordinal); _usedRemappings = new HashSet(StringComparer.Ordinal); _filePath = ""; @@ -1680,6 +1682,7 @@ public void GenerateBindings(TranslationUnit translationUnit, string filePath, s _overloadIndices.Clear(); _isExcluded.Clear(); _fileContents.Clear(); + _fileNames.Clear(); if (translationUnit.Handle.NumDiagnostics != 0) {