Skip to content

Commit 168763b

Browse files
Add MSBuild.Benchmarks and greatly improve ItemSpecModifiers and BuiltInMetadata performance (#13386)
I recommend reviewing this pull request commit-by-commit. # Summary of Changes - Introduce new MSBuild.Benchmarks project for tackling performance investigations. - Add a few benchmarks for various methods on `ItemSpecModifiers`. - Make several performance fixes related to `ItemSpecModifiers` and `BuildInMetadata`. - General clean up. Across the board, most benchmarks are **3×–35× faster** with **allocations eliminated or reduced by 93–100%**. The largest wins are in repeated-access and multi-item scenarios, which are the most representative of real build workloads. The full details are below but here are the highlights. ## Highlights ### 🚀 Speed Improvements (.NET 10.0) | Benchmark | Before | After | Speedup | |---|---|---|---| | `IsItemSpecModifier_AllModifiers` | 151.4 ns | 38.5 ns | **3.9×** | | `GetItemSpecModifier_DefiningProjectDirectory` | 812.2 ns | 72.4 ns | **11.2×** | | `TaskItem_AllDerivableModifiers_Once` | 434.5 ns | 87.4 ns | **5.0×** | | `TaskItem_FilenameAndExtension_Repeated` | 908.5 ns | 173.7 ns | **5.2×** | | `TaskItem_Filename_ManyItems` | 9,651 ns | 1,857 ns | **5.2×** | | `TaskItem_FullPathDerivedModifiers_Repeated` | 2,128 ns | 321.6 ns | **6.6×** | | `TaskItem_DefiningProjectDirectory_Repeated` | 9,121 ns | 970.8 ns | **9.4×** | | `PI_DefiningProjectDirectory_Repeated` | 9,092 ns | 1,021 ns | **8.9×** | | `PI_DefiningProjectFullPath_AllItems_Multi` | 35,245 ns | 9,947 ns | **3.5×** | | `PI_DefiningProjectDir_AllItems_Multi_Repeated` | 878,931 ns | 103,961 ns | **8.5×** | | `PI_FilenameExtension_AllItems` | 22,698 ns | 5,343 ns | **4.2×** | | `PI_FilenameExtension_AllItems_Repeated` | 181,612 ns | 67,028 ns | **2.7×** | ### 🧹 Allocation Reductions (.NET 10.0) | Benchmark | Before | After | Reduction | |---|---|---|---| | `TaskItem_AllDerivableModifiers_Once` | 1,232 B | **0 B** | 100% | | `TaskItem_FilenameAndExtension_Repeated` | 640 B | **0 B** | 100% | | `TaskItem_Filename_ManyItems` | 7,920 B | **0 B** | 100% | | `TaskItem_FullPathDerivedModifiers_Repeated` | 7,120 B | **0 B** | 100% | | `TaskItem_DefiningProjectDirectory_Repeated` | 8,240 B | **0 B** | 100% | | `TaskItem_AllDefiningProjectModifiers_Once` | 912 B | **0 B** | 100% | | `TaskItem_DefiningProjectNameExtension_AllItems` | 8,800 B | **0 B** | 100% | | `GetItemSpecModifier_DefiningProjectDirectory` | 536 B | **0 B** | 100% | | `PI_FilenameExtension_AllItems_Repeated` | 143,840 B | **640 B** | 99.6% | | `PI_DefiningProjectDir_AllItems_Multi_Repeated` | 824,640 B | **640 B** | 99.9% | | `PI_FilenameExtension_AllItems` | 14,384 B | **64 B** | 99.6% | | `PI_DefiningProjectDirectory_Repeated` | 8,304 B | **64 B** | 99.2% | | `PI_AllDerivableModifiers_Once` | 1,296 B | **64 B** | 95.1% | | `PI_AllDefiningProjectModifiers_Once` | 976 B | **64 B** | 93.4% | ### 📊 .NET Framework 4.8.1 | Benchmark | Before | After | Speedup | |---|---|---|---| | `GetItemSpecModifier_DefiningProjectDirectory` | 5,467 ns | 156.8 ns | **34.9×** | | `TaskItem_DefiningProjectDirectory_Repeated` | 58,025 ns | 2,539 ns | **22.9×** | | `PI_DefiningProjectDir_AllItems_Multi_Repeated` | 6,399 μs | 282.3 μs | **22.7×** | | `TaskItem_Filename_ManyItems` | 110,078 ns | 6,916 ns | **15.9×** | | `TaskItem_FullPathDerivedModifiers_Repeated` | 26,619 ns | 2,262 ns | **11.8×** | | `PI_FilenameExtension_AllItems_Repeated` | 2,162 μs | 202.6 μs | **10.7×** | | `TaskItem_AllDerivableModifiers_Once` | 5,322 ns | 507.2 ns | **10.5×** | | `PI_FilenameExtension_AllItems` | 216,406 ns | 20,366 ns | **10.6×** | | `TaskItem_FilenameAndExtension_Repeated` | 10,238 ns | 664.7 ns | **15.4×** | | `PI_AllDerivableModifiers_Once` | 5,525 ns | 688.6 ns | **8.0×** | | `PI_DefiningProjectDirectory_Repeated` | 64,204 ns | 2,796 ns | **23.0×** | | `PI_AllDefiningProjectModifiers_Once` | 9,808 ns | 1,154 ns | **8.5×** | | `TaskItem_AllDefiningProjectModifiers_Once` | 8,636 ns | 965.3 ns | **8.9×** | ## 'Quick-and-Dirty' Telemetry I had Copilot write some "quick-and-dirty" telemetry to track information in `ItemSpecModifiers` and dump it to a file. I built MSBuild with that extra telemetry and then built Roslyn (starting from `Microsoft.VisualStudio.LanguageServices.CSharp.csproj`) using that MSBuild. This gave me a dump with loads of interesting details. For example, `IsItemSpecModifier` was called 901,057 times. <details> <summary><b>Full Quick-and-Dirty Telemetry Dump</b></summary> ``` === ItemSpecModifiers Telemetry (2026-03-12T09:38:41.0365991-07:00) === --- Top-level method calls --- IsItemSpecModifier: 901,057 IsDerivableItemSpecModifier: 144,857 GetItemSpecModifier: 180,890 --- Compute helper calls --- ComputeFullPath: 75,443 ComputeRootDir: 84 ComputeFilename: 57,426 ComputeExtension: 32,071 ComputeRelativeDir: 22 ComputeDirectory: 84 ComputeModifiedTime: 0 ComputeCreatedTime: 0 ComputeAccessedTime: 0 --- Per-modifier breakdown (inside GetItemSpecModifier) --- FullPath: 44,783 RootDir: 41 Filename: 57,426 Extension: 6,462 RelativeDir: 22 Directory: 41 RecursiveDir: 0 Identity: 15,814 ModifiedTime: 0 CreatedTime: 0 AccessedTime: 0 DefiningProjectFullPath: 30,535 DefiningProjectDirectory: 43 DefiningProjectName: 0 DefiningProjectExtension: 25,609 ========================================================== Per-ItemSpec Modifier Hit Matrix ========================================================== Unique item specs seen: 13,809 Item specs hit > 1 time: 13,005 Grand total modifier lookups: 180,890 --- Top 50 hottest item specs (by total modifier calls) --- #1 (1,502 calls): System.Runtime.CompilerServices.InternalsVisibleTo Identity 751 DefiningProjectFullPath 751 #2 (380 calls): D:\Projects\roslyn\artifacts\bin\Microsoft.CodeAnalysis\Debug\netstandard2.0\Microsoft.CodeAnalysis.dll FullPath 17 Filename 280 Extension 40 Identity 21 DefiningProjectFullPath 22 #3 (270 calls): D:\Projects\roslyn\artifacts\bin\Microsoft.CodeAnalysis.Scripting\Debug\netstandard2.0\Microsoft.CodeAnalysis.Scripting.dll FullPath 12 Filename 195 Extension 30 Identity 16 DefiningProjectFullPath 17 #4 (270 calls): D:\Projects\roslyn\artifacts\bin\Microsoft.CodeAnalysis.Workspaces\Debug\netstandard2.0\Microsoft.CodeAnalysis.Workspaces.dll FullPath 12 Filename 195 Extension 30 Identity 16 DefiningProjectFullPath 17 #5 (247 calls): D:\.nuget\packages\system.threading.tasks.extensions\4.6.3\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll FullPath 13 Filename 221 Identity 13 #6 (247 calls): D:\.nuget\packages\system.memory\4.6.3\lib\netstandard2.0\System.Memory.dll FullPath 13 Filename 221 Identity 13 #7 (247 calls): D:\.nuget\packages\system.buffers\4.6.1\lib\netstandard2.0\System.Buffers.dll FullPath 13 Filename 221 Identity 13 #8 (247 calls): D:\.nuget\packages\system.numerics.vectors\4.6.1\lib\netstandard2.0\System.Numerics.Vectors.dll FullPath 13 Filename 221 Identity 13 #9 (247 calls): D:\.nuget\packages\system.text.encoding.codepages\8.0.0\lib\netstandard2.0\System.Text.Encoding.CodePages.dll FullPath 13 Filename 221 Identity 13 #10 (247 calls): D:\.nuget\packages\system.runtime.compilerservices.unsafe\6.1.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll FullPath 13 Filename 221 Identity 13 #11 (245 calls): D:\.nuget\packages\microsoft.dotnet.arcade.sdk\10.0.0-beta.26160.1\tools\Assets\DotNetPackageIcon.png FullPath 48 Filename 40 Extension 70 Identity 2 DefiningProjectFullPath 37 DefiningProjectDirectory 8 DefiningProjectExtension 40 #12 (245 calls): D:\Projects\roslyn\eng\targets\..\..\src\NuGet\ThirdPartyNotices.rtf FullPath 48 Filename 40 Extension 70 Identity 2 DefiningProjectFullPath 37 DefiningProjectDirectory 8 DefiningProjectExtension 40 #13 (240 calls): D:\.nuget\packages\system.collections.immutable\10.0.1\lib\netstandard2.0\System.Collections.Immutable.dll FullPath 12 Filename 216 Identity 12 #14 (240 calls): D:\.nuget\packages\system.reflection.metadata\10.0.1\lib\netstandard2.0\System.Reflection.Metadata.dll FullPath 12 Filename 216 Identity 12 #15 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Web.dll FullPath 13 Filename 208 Identity 13 #16 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Threading.ThreadPool.dll FullPath 13 Filename 208 Identity 13 #17 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.IO.FileSystem.Watcher.dll FullPath 13 Filename 208 Identity 13 #18 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Resources.ResourceManager.dll FullPath 13 Filename 208 Identity 13 #19 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.IO.dll FullPath 13 Filename 208 Identity 13 #20 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.IO.Pipes.dll FullPath 13 Filename 208 Identity 13 #21 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.IO.MemoryMappedFiles.dll FullPath 13 Filename 208 Identity 13 #22 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.ValueTuple.dll FullPath 13 Filename 208 Identity 13 #23 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Console.dll FullPath 13 Filename 208 Identity 13 #24 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Linq.Parallel.dll FullPath 13 Filename 208 Identity 13 #25 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Security.Claims.dll FullPath 13 Filename 208 Identity 13 #26 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.IO.UnmanagedMemoryStream.dll FullPath 13 Filename 208 Identity 13 #27 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Threading.Overlapped.dll FullPath 13 Filename 208 Identity 13 #28 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Drawing.Primitives.dll FullPath 13 Filename 208 Identity 13 #29 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Net.WebSockets.dll FullPath 13 Filename 208 Identity 13 #30 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Security.Cryptography.Algorithms.dll FullPath 13 Filename 208 Identity 13 #31 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.IO.FileSystem.DriveInfo.dll FullPath 13 Filename 208 Identity 13 #32 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Diagnostics.Tracing.dll FullPath 13 Filename 208 Identity 13 #33 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Linq.Expressions.dll FullPath 13 Filename 208 Identity 13 #34 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Security.Principal.dll FullPath 13 Filename 208 Identity 13 #35 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Globalization.Calendars.dll FullPath 13 Filename 208 Identity 13 #36 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Security.Cryptography.Primitives.dll FullPath 13 Filename 208 Identity 13 #37 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.ObjectModel.dll FullPath 13 Filename 208 Identity 13 #38 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Xml.XDocument.dll FullPath 13 Filename 208 Identity 13 #39 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Collections.dll FullPath 13 Filename 208 Identity 13 #40 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.ComponentModel.TypeConverter.dll FullPath 13 Filename 208 Identity 13 #41 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Runtime.InteropServices.dll FullPath 13 Filename 208 Identity 13 #42 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Collections.Concurrent.dll FullPath 13 Filename 208 Identity 13 #43 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Collections.Specialized.dll FullPath 13 Filename 208 Identity 13 #44 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Dynamic.Runtime.dll FullPath 13 Filename 208 Identity 13 #45 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Net.Requests.dll FullPath 13 Filename 208 Identity 13 #46 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Security.Cryptography.X509Certificates.dll FullPath 13 Filename 208 Identity 13 #47 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Net.dll FullPath 13 Filename 208 Identity 13 #48 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.IO.Compression.ZipFile.dll FullPath 13 Filename 208 Identity 13 #49 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Xml.XmlSerializer.dll FullPath 13 Filename 208 Identity 13 #50 (234 calls): D:\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\System.Runtime.CompilerServices.VisualC.dll FullPath 13 Filename 208 Identity 13 --- Repetition histogram (total modifier calls per item spec → count of item specs) --- 1,502 calls → 1 item specs 380 calls → 1 item specs 270 calls → 2 item specs 247 calls → 6 item specs 245 calls → 2 item specs 240 calls → 2 item specs 234 calls → 112 item specs 226 calls → 1 item specs 224 calls → 2 item specs 204 calls → 4 item specs 196 calls → 1 item specs 164 calls → 13 item specs 160 calls → 2 item specs 151 calls → 3 item specs 144 calls → 53 item specs 140 calls → 6 item specs 138 calls → 4 item specs 134 calls → 1 item specs 132 calls → 7 item specs 122 calls → 2 item specs 117 calls → 14 item specs 113 calls → 124 item specs 111 calls → 1 item specs 110 calls → 2 item specs 109 calls → 1 item specs 101 calls → 3 item specs 100 calls → 2 item specs 99 calls → 1 item specs 98 calls → 1 item specs 91 calls → 5 item specs 89 calls → 59 item specs 86 calls → 2 item specs 84 calls → 1 item specs 82 calls → 2 item specs 80 calls → 7 item specs 78 calls → 1 item specs 76 calls → 2 item specs 75 calls → 1 item specs 74 calls → 5 item specs 73 calls → 3 item specs 72 calls → 2 item specs 70 calls → 1 item specs 66 calls → 5 item specs 65 calls → 5 item specs 60 calls → 10 item specs 58 calls → 8 item specs 57 calls → 1 item specs 56 calls → 1 item specs 54 calls → 6 item specs 52 calls → 3 item specs 51 calls → 8 item specs 50 calls → 1 item specs 49 calls → 2 item specs 48 calls → 3 item specs 47 calls → 1 item specs 46 calls → 1 item specs 45 calls → 13 item specs 44 calls → 20 item specs 41 calls → 2 item specs 40 calls → 8 item specs 39 calls → 1 item specs 37 calls → 4 item specs 36 calls → 51 item specs 35 calls → 5 item specs 34 calls → 1 item specs 33 calls → 1 item specs 32 calls → 2 item specs 30 calls → 4 item specs 29 calls → 11 item specs 28 calls → 20 item specs 27 calls → 11 item specs 26 calls → 16 item specs 24 calls → 30 item specs 23 calls → 3 item specs 22 calls → 153 item specs 21 calls → 1 item specs 20 calls → 26 item specs 19 calls → 1 item specs 18 calls → 109 item specs 16 calls → 30 item specs 15 calls → 35 item specs 14 calls → 383 item specs 13 calls → 166 item specs 12 calls → 1,051 item specs 11 calls → 1 item specs 10 calls → 70 item specs 9 calls → 43 item specs 8 calls → 7,012 item specs 7 calls → 50 item specs 6 calls → 910 item specs 5 calls → 1,420 item specs 4 calls → 449 item specs 3 calls → 32 item specs 2 calls → 340 item specs 1 calls → 804 item specs ``` </details> ## Initial Benchmark Results ### .NET 10.0 | Method | Mean | Error | StdDev | Gen0 | Allocated | |--------------------------------------------- |--------------:|------------:|------------:|-------:|----------:| | IsItemSpecModifier_AllModifiers | 151.425 ns | 0.1309 ns | 0.1161 ns | - | - | | IsDerivableItemSpecModifier_RecursiveDir | 2.522 ns | 0.0028 ns | 0.0025 ns | - | - | | GetItemSpecModifier_FullPath | 267.552 ns | 0.3441 ns | 0.3050 ns | - | - | | GetItemSpecModifier_Directory | 359.023 ns | 0.7603 ns | 0.6740 ns | 0.0224 | 376 B | | GetItemSpecModifier_ModifiedTime | 28,544.061 ns | 237.0185 ns | 221.7073 ns | - | 176 B | | GetItemSpecModifier_DefiningProjectDirectory | 812.229 ns | 3.3636 ns | 2.9817 ns | 0.0315 | 536 B | | TaskItem_AllDerivableModifiers_Once | 434.5 ns | 4.36 ns | 4.08 ns | 0.0734 | 1232 B | | TaskItem_FilenameAndExtension_Repeated | 908.5 ns | 1.13 ns | 1.00 ns | 0.0381 | 640 B | | TaskItem_Filename_ManyItems | 9,651.3 ns | 29.02 ns | 25.72 ns | 0.4730 | 7920 B | | TaskItem_FullPathDerivedModifiers_Repeated | 2,127.6 ns | 7.45 ns | 6.60 ns | 0.4234 | 7120 B | | ProjectItemInstance_AllDerivableModifiers_Once | 513.4 ns | 2.93 ns | 2.74 ns | 0.0772 | 1296 B | | ProjectItemInstance_FilenameExtension_AllItems | 22,697.6 ns | 68.62 ns | 64.19 ns | 0.8545 | 14384 B | | ProjectItemInstance_FilenameExtension_AllItems_Repeated | 181,612.2 ns | 1,125.69 ns | 940.00 ns | 8.5449 | 143840 B | | ProjectItemInstance_AllDefiningProjectModifiers_Once | 1.559 μs | 0.0047 μs | 0.0040 μs | 0.0572 | 976 B | | ProjectItemInstance_DefiningProjectDirectory_Repeated | 9.092 μs | 0.0654 μs | 0.0580 μs | 0.4883 | 8304 B | | ProjectItemInstance_DefiningProjectNameExtension_AllItems_SingleProject | 12.515 μs | 0.0297 μs | 0.0263 μs | 0.5188 | 8864 B | | ProjectItemInstance_DefiningProjectFullPath_AllItems_MultiProject | 35.245 μs | 0.0405 μs | 0.0379 μs | - | 64 B | | ProjectItemInstance_DefiningProjectDirectory_AllItems_MultiProject_Repeated | 878.931 μs | 3.1495 μs | 2.7919 μs | 48.8281 | 824640 B | | TaskItem_AllDefiningProjectModifiers_Once | 1.430 μs | 0.0027 μs | 0.0023 μs | 0.0534 | 912 B | | TaskItem_DefiningProjectNameExtension_AllItems | 11.628 μs | 0.0289 μs | 0.0241 μs | 0.5188 | 8800 B | | TaskItem_DefiningProjectDirectory_Repeated | 9.121 μs | 0.0192 μs | 0.0170 μs | 0.4883 | 8240 B | ### .NET Framework 4.8.1 | Method | Mean | Error | StdDev | Gen0 | Allocated | |--------------------------------------------- |-------------:|-----------:|----------:|-------:|----------:| | IsItemSpecModifier_AllModifiers | 227.45 ns | 0.297 ns | 0.264 ns | - | - | | IsDerivableItemSpecModifier_RecursiveDir | 10.49 ns | 0.019 ns | 0.018 ns | - | - | | GetItemSpecModifier_FullPath | 1,267.45 ns | 0.844 ns | 0.705 ns | - | - | | GetItemSpecModifier_Directory | 2,433.43 ns | 7.400 ns | 6.560 ns | 0.0954 | 517 B | | GetItemSpecModifier_ModifiedTime | 41,881.06 ns | 111.954 ns | 93.487 ns | 0.1221 | 878 B | | GetItemSpecModifier_DefiningProjectDirectory | 5,467.24 ns | 8.295 ns | 6.926 ns | 0.1602 | 857 B | | TaskItem_AllDerivableModifiers_Once | 5.322 μs | 0.0354 μs | 0.0331 μs | 0.3662 | 1923 B | | TaskItem_FilenameAndExtension_Repeated | 10.238 μs | 0.0108 μs | 0.0101 μs | 0.1373 | 761 B | | TaskItem_Filename_ManyItems | 110.078 μs | 0.1367 μs | 0.1212 μs | 2.3193 | 12379 B | | TaskItem_FullPathDerivedModifiers_Repeated | 26.619 μs | 0.1695 μs | 0.1585 μs | 2.1973 | 11578 B | | ProjectItemInstance_AllDerivableModifiers_Once | 5.525 μs | 0.0311 μs | 0.0276 μs | 0.3662 | 1959 B | | ProjectItemInstance_FilenameExtension_AllItems | 216.406 μs | 0.2767 μs | 0.2160 μs | 2.9297 | 16422 B | | ProjectItemInstance_FilenameExtension_AllItems_Repeated | 2,161.623 μs | 10.4073 μs | 9.2258 μs | 31.2500 | 164225 B | | ProjectItemInstance_AllDefiningProjectModifiers_Once | 9.808 μs | 0.0177 μs | 0.0165 μs | 0.2594 | 1418 B | | ProjectItemInstance_DefiningProjectDirectory_Repeated | 64.204 μs | 0.3983 μs | 0.3726 μs | 2.3193 | 12616 B | | ProjectItemInstance_DefiningProjectNameExtension_AllItems_SingleProject | 131.504 μs | 0.3383 μs | 0.3165 μs | 2.1973 | 12456 B | | ProjectItemInstance_DefiningProjectFullPath_AllItems_MultiProject | 173.171 μs | 0.7242 μs | 0.5654 μs | - | 38 B | | ProjectItemInstance_DefiningProjectDirectory_AllItems_MultiProject_Repeated | 6,399.063 μs | 71.6927 μs | 67.0614 μs | 234.3750 | 1242225 B | | TaskItem_AllDefiningProjectModifiers_Once | 8.636 μs | 0.0177 μs | 0.0147 μs | 0.2594 | 1382 B | | TaskItem_DefiningProjectNameExtension_AllItems | 126.982 μs | 0.2250 μs | 0.2104 μs | 2.1973 | 12420 B | | TaskItem_DefiningProjectDirectory_Repeated | 58.025 μs | 0.1388 μs | 0.1299 μs | 2.3804 | 12579 B | ## Final Benchmark Results ### .NET 10.0 | Method | Mean | Error | StdDev | Gen0 | Allocated | |--------------------------------------------- |--------------:|------------:|------------:|-------:|----------:| | IsItemSpecModifier_AllModifiers | 38.4923 ns | 0.5501 ns | 0.5145 ns | - | - | | IsDerivableItemSpecModifier_RecursiveDir | 0.0000 ns | 0.0000 ns | 0.0000 ns | - | - | | GetItemSpecModifier_FullPath | 260.1019 ns | 0.3623 ns | 0.3212 ns | - | - | | GetItemSpecModifier_Directory | 413.2121 ns | 1.9441 ns | 1.8185 ns | 0.0224 | 376 B | | GetItemSpecModifier_ModifiedTime | 30,272.3116 ns | 408.0450 ns | 381.6855 ns | - | 176 B | | GetItemSpecModifier_DefiningProjectDirectory | 72.3691 ns | 0.0289 ns | 0.0270 ns | - | - | | TaskItem_AllDerivableModifiers_Once | 87.41 ns | 0.226 ns | 0.200 ns | - | - | | TaskItem_FilenameAndExtension_Repeated | 173.68 ns | 1.812 ns | 1.695 ns | - | - | | TaskItem_Filename_ManyItems | 1,856.79 ns | 1.128 ns | 0.942 ns | - | - | | TaskItem_FullPathDerivedModifiers_Repeated | 321.57 ns | 0.301 ns | 0.267 ns | - | - | | ProjectItemInstance_AllDerivableModifiers_Once | 143.78 ns | 0.493 ns | 0.462 ns | 0.0038 | 64 B | | ProjectItemInstance_FilenameExtension_AllItems | 5,343.30 ns | 4.955 ns | 4.138 ns | - | 64 B | | ProjectItemInstance_FilenameExtension_AllItems_Repeated | 67,028.30 ns | 252.985 ns | 224.264 ns | - | 640 B | | ProjectItemInstance_AllDefiningProjectModifiers_Once | 459.4 ns | 0.34 ns | 0.31 ns | 0.0038 | 64 B | | ProjectItemInstance_DefiningProjectDirectory_Repeated | 1,020.5 ns | 0.56 ns | 0.47 ns | 0.0038 | 64 B | | ProjectItemInstance_DefiningProjectNameExtension_AllItems_SingleProject | 20,091.0 ns | 17.96 ns | 16.80 ns | - | 64 B | | ProjectItemInstance_DefiningProjectFullPath_AllItems_MultiProject | 9,946.7 ns | 5.28 ns | 4.68 ns | - | 64 B | | ProjectItemInstance_DefiningProjectDirectory_AllItems_MultiProject_Repeated | 103,960.5 ns | 57.74 ns | 48.21 ns | - | 640 B | | TaskItem_AllDefiningProjectModifiers_Once | 400.5 ns | 0.15 ns | 0.12 ns | - | - | | TaskItem_DefiningProjectNameExtension_AllItems | 19,150.3 ns | 11.55 ns | 10.24 ns | - | - | | TaskItem_DefiningProjectDirectory_Repeated | 970.8 ns | 0.62 ns | 0.55 ns | - | - | ### .NET Framework 4.8.1 | Method | Mean | Error | StdDev | Gen0 | Allocated | |--------------------------------------------- |-------------:|-----------:|----------:|-------:|----------:| | IsItemSpecModifier_AllModifiers | 106.532 ns | 0.4289 ns | 0.4012 ns | - | - | | IsDerivableItemSpecModifier_RecursiveDir | 5.067 ns | 0.0031 ns | 0.0024 ns | - | - | | GetItemSpecModifier_FullPath | 1,331.138 ns | 1.4666 ns | 1.3001 ns | - | - | | GetItemSpecModifier_Directory | 2,471.887 ns | 4.2333 ns | 3.7527 ns | 0.0954 | 517 B | | GetItemSpecModifier_ModifiedTime | 44,773.794 ns | 405.2647 ns | 359.2566 ns | 0.1221 | 878 B | | GetItemSpecModifier_DefiningProjectDirectory | 156.840 ns | 0.2242 ns | 0.1987 ns | - | - | | TaskItem_AllDerivableModifiers_Once | 507.2 ns | 1.19 ns | 1.11 ns | - | - | | TaskItem_FilenameAndExtension_Repeated | 664.7 ns | 0.39 ns | 0.35 ns | - | - | | TaskItem_Filename_ManyItems | 6,916.2 ns | 4.03 ns | 3.77 ns | - | - | | TaskItem_FullPathDerivedModifiers_Repeated | 2,261.6 ns | 3.34 ns | 2.96 ns | - | - | | ProjectItemInstance_AllDerivableModifiers_Once | 688.6 ns | 1.01 ns | 0.94 ns | 0.0067 | 36 B | | ProjectItemInstance_FilenameExtension_AllItems | 20,366.3 ns | 11.28 ns | 10.56 ns | - | 36 B | | ProjectItemInstance_FilenameExtension_AllItems_Repeated | 202,570.4 ns | 271.12 ns | 240.34 ns | - | 362 B | | ProjectItemInstance_AllDefiningProjectModifiers_Once | 1,153.5 ns | 13.83 ns | 12.26 ns | 0.0057 | 36 B | | ProjectItemInstance_DefiningProjectDirectory_Repeated | 2,795.7 ns | 17.33 ns | 16.21 ns | 0.0038 | 36 B | | ProjectItemInstance_DefiningProjectNameExtension_AllItems_SingleProject | 46,236.9 ns | 105.16 ns | 93.22 ns | - | 36 B | | ProjectItemInstance_DefiningProjectFullPath_AllItems_MultiProject | 28,362.1 ns | 34.50 ns | 30.58 ns | - | 36 B | | ProjectItemInstance_DefiningProjectDirectory_AllItems_MultiProject_Repeated | 282,316.9 ns | 1,944.00 ns | 1,818.42 ns | - | 364 B | | TaskItem_AllDefiningProjectModifiers_Once | 965.3 ns | 5.43 ns | 4.53 ns | - | - | | TaskItem_DefiningProjectNameExtension_AllItems | 42,707.8 ns | 179.84 ns | 168.23 ns | - | - | | TaskItem_DefiningProjectDirectory_Repeated | 2,539.1 ns | 9.64 ns | 8.05 ns | - | - |
1 parent 37cc8b7 commit 168763b

23 files changed

Lines changed: 1437 additions & 403 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ artifacts/
1717
.dotnet/
1818
.tools/
1919
.packages/
20+
BenchmarkDotNet.Artifacts/
2021

2122
# Visual Studio 2015 cache/options directory
2223
.vs/

MSBuild.Dev.slnf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"src\\Framework\\Microsoft.Build.Framework.csproj",
1111
"src\\MSBuild.UnitTests\\Microsoft.Build.CommandLine.UnitTests.csproj",
1212
"src\\MSBuild\\MSBuild.csproj",
13+
"src\\MSBuild.Benchmarks\\MSBuild.Benchmarks.csproj",
1314
"src\\StringTools\\StringTools.csproj",
1415
"src\\Tasks.UnitTests\\Microsoft.Build.Tasks.UnitTests.csproj",
1516
"src\\Tasks\\Microsoft.Build.Tasks.csproj",

MSBuild.slnx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@
6767
<Project Path="src/Framework/Microsoft.Build.Framework.csproj">
6868
<Platform Solution="*|x64" Project="x64" />
6969
</Project>
70+
<Project Path="src/MSBuild.Benchmarks/MSBuild.Benchmarks.csproj" Id="7ab529db-7954-4d4f-a186-7544008a3b46">
71+
<Platform Solution="*|ARM64" Project="arm64" />
72+
<Platform Solution="*|x64" Project="x64" />
73+
</Project>
7074
<Project Path="src/MSBuild.Bootstrap/MSBuild.Bootstrap.csproj">
7175
<Platform Solution="*|x64" Project="x64" />
7276
</Project>

eng/dependabot/Directory.Packages.props

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010
these properties to override package versions if necessary. -->
1111

1212
<ItemGroup>
13-
<PackageVersion Include="BenchmarkDotNet" Version="0.13.10" />
13+
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
1414
<PackageVersion Update="BenchmarkDotNet" Condition="'$(BenchmarkDotNetVersion)' != ''" Version="$(BenchmarkDotNetVersion)" />
1515

16+
<PackageVersion Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.12" />
17+
<PackageVersion Update="BenchmarkDotNet.Diagnostics.Windows" Condition="'$(BenchmarkDotNetDiagnosticsWindowsVersion)' != ''" Version="$(BenchmarkDotNetDiagnosticsWindowsVersion)" />
18+
1619
<PackageVersion Include="AwesomeAssertions" Version="8.0.2" />
1720
<PackageVersion Update="AwesomeAssertions" Condition="'$(AwesomeAssertionsVersion)' != ''" Version="$(AwesomeAssertionsVersion)" />
1821

src/Build/BackEnd/BuildManager/BuildManager.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,7 @@ public void EndBuild()
11051105
}
11061106

11071107
TaskRouter.ClearCache();
1108+
ItemSpecModifiers.ClearDefiningProjectCache();
11081109
}
11091110
catch (Exception e)
11101111
{

src/Build/Definition/BuiltInMetadata.cs

Lines changed: 39 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5-
using System.Collections.Generic;
5+
using System.Collections.Immutable;
66
using System.Diagnostics;
77
using Microsoft.Build.Framework;
88
using Microsoft.Build.Shared;
@@ -19,22 +19,12 @@ internal static class BuiltInMetadata
1919
/// <summary>
2020
/// Retrieves the count of built-in metadata.
2121
/// </summary>
22-
internal static int MetadataCount
23-
{
24-
[DebuggerStepThrough]
25-
get
26-
{ return ItemSpecModifiers.All.Length; }
27-
}
22+
internal static int MetadataCount => ItemSpecModifiers.All.Length;
2823

2924
/// <summary>
3025
/// Retrieves the list of metadata names.
3126
/// </summary>
32-
internal static ICollection<string> MetadataNames
33-
{
34-
[DebuggerStepThrough]
35-
get
36-
{ return ItemSpecModifiers.All; }
37-
}
27+
internal static ImmutableArray<string> MetadataNames => ItemSpecModifiers.All;
3828

3929
/// <summary>
4030
/// Retrieves a built-in metadata value and caches it.
@@ -48,45 +38,53 @@ internal static ICollection<string> MetadataNames
4838
/// <param name="evaluatedIncludeEscaped">The evaluated include for the item.</param>
4939
/// <param name="definingProjectEscaped">The path to the project that defined this item</param>
5040
/// <param name="name">The name of the metadata.</param>
51-
/// <param name="fullPath">The generated full path, for caching</param>
41+
/// <param name="cache">The generated full path, for caching</param>
5242
/// <returns>The unescaped metadata value.</returns>
53-
internal static string GetMetadataValue(string currentDirectory, string evaluatedIncludeBeforeWildcardExpansionEscaped, string evaluatedIncludeEscaped, string definingProjectEscaped, string name, ref string fullPath)
54-
{
55-
return EscapingUtilities.UnescapeAll(GetMetadataValueEscaped(currentDirectory, evaluatedIncludeBeforeWildcardExpansionEscaped, evaluatedIncludeEscaped, definingProjectEscaped, name, ref fullPath));
56-
}
43+
internal static string GetMetadataValue(
44+
string currentDirectory,
45+
string evaluatedIncludeBeforeWildcardExpansionEscaped,
46+
string evaluatedIncludeEscaped,
47+
string definingProjectEscaped,
48+
string name,
49+
ref ItemSpecModifiers.Cache cache)
50+
=> EscapingUtilities.UnescapeAll(GetMetadataValueEscaped(currentDirectory, evaluatedIncludeBeforeWildcardExpansionEscaped, evaluatedIncludeEscaped, definingProjectEscaped, name, ref cache));
5751

5852
/// <summary>
59-
/// Retrieves a built-in metadata value and caches it.
53+
/// Retrieves a built-in metadata value, caching derivable results in the provided per-item cache.
6054
/// If value is not available, returns empty string.
6155
/// </summary>
62-
/// <param name="currentDirectory">
63-
/// The current directory for evaluation. Null if this is being called from a task, otherwise
64-
/// it should be the project's directory.
65-
/// </param>
66-
/// <param name="evaluatedIncludeBeforeWildcardExpansionEscaped">The evaluated include prior to wildcard expansion.</param>
67-
/// <param name="evaluatedIncludeEscaped">The evaluated include for the item.</param>
68-
/// <param name="definingProjectEscaped">The path to the project that defined this item</param>
69-
/// <param name="name">The name of the metadata.</param>
70-
/// <param name="fullPath">The generated full path, for caching</param>
71-
/// <returns>The escaped as necessary metadata value.</returns>
72-
internal static string GetMetadataValueEscaped(string currentDirectory, string evaluatedIncludeBeforeWildcardExpansionEscaped, string evaluatedIncludeEscaped, string definingProjectEscaped, string name, ref string fullPath)
56+
internal static string GetMetadataValueEscaped(
57+
string currentDirectory,
58+
string evaluatedIncludeBeforeWildcardExpansionEscaped,
59+
string evaluatedIncludeEscaped,
60+
string definingProjectEscaped,
61+
string name,
62+
ref ItemSpecModifiers.Cache cache)
7363
{
74-
// This is an assert, not a VerifyThrow, because the caller should already have done this check, and it's slow/hot.
75-
Debug.Assert(ItemSpecModifiers.IsItemSpecModifier(name));
76-
77-
string value;
78-
if (String.Equals(name, ItemSpecModifiers.RecursiveDir, StringComparison.OrdinalIgnoreCase))
64+
if (ItemSpecModifiers.TryGetModifierKind(name, out ItemSpecModifierKind modifierKind))
7965
{
80-
value = GetRecursiveDirValue(evaluatedIncludeBeforeWildcardExpansionEscaped, evaluatedIncludeEscaped);
81-
}
82-
else
83-
{
84-
value = ItemSpecModifiers.GetItemSpecModifier(currentDirectory, evaluatedIncludeEscaped, definingProjectEscaped, name, ref fullPath);
66+
return GetMetadataValueEscaped(currentDirectory, evaluatedIncludeBeforeWildcardExpansionEscaped, evaluatedIncludeEscaped, definingProjectEscaped, modifierKind, ref cache);
8567
}
8668

87-
return value;
69+
Debug.Fail($"Expected a valid item-spec modifier, got \"{name}\".");
70+
return string.Empty;
8871
}
8972

73+
/// <summary>
74+
/// Retrieves a built-in metadata value, caching derivable results in the provided per-item cache.
75+
/// If value is not available, returns empty string.
76+
/// </summary>
77+
internal static string GetMetadataValueEscaped(
78+
string currentDirectory,
79+
string evaluatedIncludeBeforeWildcardExpansionEscaped,
80+
string evaluatedIncludeEscaped,
81+
string definingProjectEscaped,
82+
ItemSpecModifierKind modifierKind,
83+
ref ItemSpecModifiers.Cache cache)
84+
=> modifierKind is ItemSpecModifierKind.RecursiveDir
85+
? GetRecursiveDirValue(evaluatedIncludeBeforeWildcardExpansionEscaped, evaluatedIncludeEscaped)
86+
: ItemSpecModifiers.GetItemSpecModifier(evaluatedIncludeEscaped, modifierKind, currentDirectory, definingProjectEscaped, ref cache);
87+
9088
/// <summary>
9189
/// Extract the value for "RecursiveDir", if any, from the Include.
9290
/// If there is none, returns an empty string.

src/Build/Definition/ProjectItem.cs

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ public class ProjectItem : IItem<ProjectMetadata>, IProjectMetadataParent, IItem
100100
private PropertyDictionary<ProjectMetadata> _directMetadata;
101101

102102
/// <summary>
103-
/// Cached value of the fullpath metadata. All other metadata are computed on demand.
103+
/// Cached values of derivable item-spec modifiers. All time-based metadata are computed on demand.
104104
/// </summary>
105-
private string _fullPath;
105+
private ItemSpecModifiers.Cache _cachedModifiers;
106106

107107
/// <summary>
108108
/// External projects support
@@ -299,12 +299,7 @@ public ICollection<ProjectMetadata> Metadata
299299
/// Includes any metadata inherited from item definitions.
300300
/// Includes both custom and built-in metadata.
301301
/// </summary>
302-
public int MetadataCount
303-
{
304-
[DebuggerStepThrough]
305-
get
306-
{ return Metadata.Count + ItemSpecModifiers.All.Length; }
307-
}
302+
public int MetadataCount => Metadata.Count + ItemSpecModifiers.All.Length;
308303

309304
/// <summary>
310305
/// Implementation of IKeyed exposing the item type, so items
@@ -700,7 +695,7 @@ public void Rename(string name)
700695
return;
701696
}
702697

703-
_fullPath = null; // Clear cached value
698+
_cachedModifiers.Clear(); // Clear cached values
704699

705700
if (_xml.Count == 0 /* no metadata */ && _project.IsSuitableExistingItemXml(_xml, name, null /* no metadata */) && !FileMatcher.HasWildcardsSemicolonItemOrPropertyReferences(name))
706701
{
@@ -854,16 +849,9 @@ internal void SplitOwnItemElement()
854849
/// the specified name, if any.
855850
/// </summary>
856851
private string GetBuiltInMetadataEscaped(string name)
857-
{
858-
string value = null;
859-
860-
if (ItemSpecModifiers.IsItemSpecModifier(name))
861-
{
862-
value = BuiltInMetadata.GetMetadataValueEscaped(_project.DirectoryPath, _evaluatedIncludeBeforeWildcardExpansionEscaped, _evaluatedIncludeEscaped, this.Xml.ContainingProject.FullPath, name, ref _fullPath);
863-
}
864-
865-
return value;
866-
}
852+
=> ItemSpecModifiers.TryGetModifierKind(name, out ItemSpecModifierKind modifierKind)
853+
? BuiltInMetadata.GetMetadataValueEscaped(_project.DirectoryPath, _evaluatedIncludeBeforeWildcardExpansionEscaped, _evaluatedIncludeEscaped, Xml.ContainingProject.FullPath, modifierKind, ref _cachedModifiers)
854+
: null;
867855

868856
/// <summary>
869857
/// Retrieves the named metadata from the item definition, if any.

src/Build/Evaluation/Expander.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2560,7 +2560,7 @@ internal static void ItemSpecModifierFunction(IElementLocation elementLocation,
25602560
string directoryToUse = item.Value.ProjectDirectory ?? FileUtilities.CurrentThreadWorkingDirectory ?? Directory.GetCurrentDirectory();
25612561
string definingProjectEscaped = item.Value.GetMetadataValueEscaped(ItemSpecModifiers.DefiningProjectFullPath);
25622562

2563-
result = ItemSpecModifiers.GetItemSpecModifier(directoryToUse, item.Key, definingProjectEscaped, functionName);
2563+
result = ItemSpecModifiers.GetItemSpecModifier(item.Key, functionName, directoryToUse, definingProjectEscaped);
25642564
}
25652565
// InvalidOperationException is how GetItemSpecModifier communicates invalid conditions upwards, so
25662566
// we do not want to rethrow in that case.
@@ -3328,7 +3328,7 @@ private static string GetMetadataValueFromMatch(
33283328
string directoryToUse = sourceOfMetadata.ProjectDirectory ?? FileUtilities.CurrentThreadWorkingDirectory ?? Directory.GetCurrentDirectory();
33293329
string definingProjectEscaped = sourceOfMetadata.GetMetadataValueEscaped(ItemSpecModifiers.DefiningProjectFullPath);
33303330

3331-
value = ItemSpecModifiers.GetItemSpecModifier(directoryToUse, itemSpec, definingProjectEscaped, match.Name);
3331+
value = ItemSpecModifiers.GetItemSpecModifier(itemSpec, match.Name, directoryToUse, definingProjectEscaped);
33323332
}
33333333
else
33343334
{

src/Build/Instance/ProjectItemInstance.cs

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -817,9 +817,9 @@ internal sealed class TaskItem :
817817
private IReadOnlyDictionary<string, string> _directMetadata;
818818

819819
/// <summary>
820-
/// Cached value of the fullpath metadata. All other metadata are computed on demand.
820+
/// Cached values of derivable item-spec modifiers. All time-based metadata are computed on demand.
821821
/// </summary>
822-
private string _fullPath;
822+
private ItemSpecModifiers.Cache _cachedModifiers;
823823

824824
/// <summary>
825825
/// All the item definitions that apply to this item, in order of
@@ -893,7 +893,7 @@ private TaskItem(TaskItem source, bool addOriginalItemSpec)
893893
_includeEscaped = source._includeEscaped;
894894
_includeBeforeWildcardExpansionEscaped = source._includeBeforeWildcardExpansionEscaped;
895895
source.CopyMetadataTo(this, addOriginalItemSpec);
896-
_fullPath = source._fullPath;
896+
_cachedModifiers = source._cachedModifiers;
897897
_definingFileEscaped = source._definingFileEscaped;
898898
}
899899

@@ -936,7 +936,7 @@ public string ItemSpec
936936
ErrorUtilities.VerifyThrowArgumentNull(value, "ItemSpec");
937937

938938
_includeEscaped = value;
939-
_fullPath = null; // Clear cached value
939+
_cachedModifiers.Clear(); // Clear cached values
940940
}
941941
}
942942

@@ -981,7 +981,10 @@ public ICollection MetadataNames
981981
names.Add(metadatum.Key);
982982
}
983983

984-
names.AddRange(ItemSpecModifiers.All);
984+
foreach (string name in ItemSpecModifiers.All)
985+
{
986+
names.Add(name);
987+
}
985988

986989
return names;
987990
}
@@ -1054,7 +1057,7 @@ internal string IncludeEscaped
10541057

10551058
ErrorUtilities.VerifyThrowArgumentLength(value, "IncludeEscaped");
10561059
_includeEscaped = value;
1057-
_fullPath = null; // Clear cached value
1060+
_cachedModifiers.Clear(); // Clear cached values
10581061
}
10591062
}
10601063

@@ -2081,16 +2084,9 @@ internal TaskItem DeepClone(bool isImmutable)
20812084
/// If value is not available, returns empty string.
20822085
/// </summary>
20832086
private string GetBuiltInMetadataEscaped(string name)
2084-
{
2085-
string value = String.Empty;
2086-
2087-
if (ItemSpecModifiers.IsItemSpecModifier(name))
2088-
{
2089-
value = BuiltInMetadata.GetMetadataValueEscaped(_projectDirectory, _includeBeforeWildcardExpansionEscaped, _includeEscaped, _definingFileEscaped, name, ref _fullPath);
2090-
}
2091-
2092-
return value;
2093-
}
2087+
=> ItemSpecModifiers.TryGetModifierKind(name, out ItemSpecModifierKind modifierKind)
2088+
? BuiltInMetadata.GetMetadataValueEscaped(_projectDirectory, _includeBeforeWildcardExpansionEscaped, _includeEscaped, _definingFileEscaped, modifierKind, ref _cachedModifiers)
2089+
: string.Empty;
20942090

20952091
/// <summary>
20962092
/// Retrieves the named metadata from the item definition, if any.

src/Build/Instance/ProjectMetadataInstance.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -241,12 +241,10 @@ internal static void VerifyThrowReservedName(string name)
241241
// PERF: This sequence of checks is faster than a full HashSet lookup since finding a match is an error case.
242242
// Otherwise, many keys would still match to a bucket and begin a string comparison.
243243
VerifyThrowReservedNameAllowItemSpecModifiers(name);
244-
foreach (string itemSpecModifier in ItemSpecModifiers.All)
244+
245+
if (ItemSpecModifiers.IsItemSpecModifier(name))
245246
{
246-
if (itemSpecModifier.Length == name.Length && itemSpecModifier[0] == char.ToUpperInvariant(name[0]))
247-
{
248-
ErrorUtilities.VerifyThrowArgument(!MSBuildNameIgnoreCaseComparer.Default.Equals(itemSpecModifier, name), "OM_ReservedName", name);
249-
}
247+
ErrorUtilities.ThrowArgument("OM_ReservedName", name);
250248
}
251249
}
252250

0 commit comments

Comments
 (0)