Skip to content

Fix TaskParameterTaskItem serialization perf #11638

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

ccastanedaucf
Copy link
Contributor

@ccastanedaucf ccastanedaucf commented Mar 27, 2025

Fixes

Fixes several hotspots I found while profiling RAR service serialization.

Changes Made

  • Fix effectively doubling the work in the write direction, where TaskParameterTaskItem is created to wrap an existing TaskItem, but then of its metadata is parsed one again right before serialization
  • Use MSBuildNameIgnoreCaseComparer.Default (by moving it into Framework`
  • Avoid additional call to retrieve value when enumerating dictionary returned by ITaskItem2.CloneCustomMetadataEscaped(). If this is a Utilities.TaskItem coming back from a task, doing an accessor check is non-negligible overhead due to CopyOnWriteDictionary.
  • SImplify translation logic. Prefer unidirectional ITranslator APIs over manually checking each direction or reimplementing methods, and let TaskParameterTaskItem handle its own serialization.

A bunch of small improvements but they add up. Since the out of proc TaskHost is the only piece currently using this, here's some profiles off my RAR service builds.

TaskParameterTaskItem.CopyMetadataTo()

This is often hit by ReferenceTable.SetItemMetadata in RAR. This fix here was to use IMetadataContainer.ImportMetadata() bulk set, since ImmutableDictionary performs better when operations are batched.

Before:
image
After:
image

Double ITaskItem parsing

Essentially when in the write direction the old code would construct a TaskParameterTaskItem, but later cast it to ITaskItem and parse everything out again. You can see this here where CloneCustomMetadataEscaped() is hit even though by this point we've already extracted the external TaskItem to our own instance.

Before:
image
After:
image

ItemSpec unescaped value caching

Added a simple cache around ItemSpec property accesses. Might be worth porting this into ProjectItemInstance.TaskItem and Utilities.TaskItem since this is hit very often.

I'm showing this one as a backtrace to show where this is hit.

Before:
image
After:
image

This actually appears to just no-op most of the time since most of the time is spent in String.IndexOf():
image

@ccastanedaucf
Copy link
Contributor Author

lol probably broke something while pulling this out of my RAR branch, will check UT failures later today

@ccastanedaucf ccastanedaucf force-pushed the dev/chcasta/prop-perf-2 branch from 5096b1b to 57f0123 Compare March 28, 2025 01:47
@ccastanedaucf
Copy link
Contributor Author

ccastanedaucf commented Mar 28, 2025

Updated with profile traces now that I've fully isolated it from other perf stuff. Tests passing so should be good to go 👍

@@ -891,6 +763,25 @@ public void CopyMetadataTo(ITaskItem destinationItem)
// between items, and need to know the source item where the metadata came from
string originalItemSpec = destinationItem.GetMetadata("OriginalItemSpec");

#if !TASKHOST
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tbh not sure why IMetadataContainer is conditioned out of the taskhost beside the extra source file (compiles fine with it), but I've kept it here anyways.

}
#endif

destinationItemAsMetadataContainer.ImportMetadata(metadataToImport);
Copy link
Contributor Author

@ccastanedaucf ccastanedaucf Mar 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on that profile showing SetMetadata("OrignalItemSpec") adding still time under here, maybe worth joining it into the IEnumerable to avoid immediately copying a new ImmutableDictionary right after this block - haven't checked to see if IEnumerable.Concat() or spread operator works efficiently or ends up allocating a collection. Otherwise a local function + yield return would work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant