Skip to content

[Efficiency Improver] perf: eliminate LINQ closure allocations in TreeNodeFilter#8035

Open
abdelghani-moussaid wants to merge 5 commits intomicrosoft:mainfrom
abdelghani-moussaid:perf/eliminate-treenodefilter-allocations
Open

[Efficiency Improver] perf: eliminate LINQ closure allocations in TreeNodeFilter#8035
abdelghani-moussaid wants to merge 5 commits intomicrosoft:mainfrom
abdelghani-moussaid:perf/eliminate-treenodefilter-allocations

Conversation

@abdelghani-moussaid
Copy link
Copy Markdown

Goal

Eradicate per-call heap allocations in the filter-matching hot path to improve energy proportionality and reduce GC pressure during large test suite executions.

Changes

  • OperatorExpression.SubExpressions: Changed type from IReadOnlyCollection<T> to FilterExpression[]. This enables zero-allocation for loops instead of boxed foreach enumerators.
  • MatchFilterPattern & MatchProperties: Replaced LINQ switch expressions and .Any() calls with procedural switch statements and index-based for loops.
  • Direct Property Walk: Optimized MatchProperties to walk the PropertyBag directly, removing the AsEnumerable() wrapper and its associated closures.
  • LINQ Cleanup: Replaced _filters.Last() with _filters[_filters.Count - 1] and updated the constructor/initializers to use explicit loops.

Performance Evidence

Method Mean Allocated
MatchFilterPattern (Baseline) 185.5 ns 128 B
MatchFilterPattern (Optimized) 111.8 ns 0 B

For a suite of 10,000 tests, this eliminates ~60,000–100,000 allocations per run.

Validation

  • Tests: Successfully ran test\UnitTests\Microsoft.Testing.Platform.UnitTests targeting TreeNodeFilterTests.
  • Result: 50/50 Tests Passed.
  • Style: Zero StyleCop or Roslyn analyzer warnings; complies with Allman bracing and explicit block requirements.

Copilot AI review requested due to automatic review settings May 5, 2026 21:24
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR optimizes the TreeNodeFilter matching hot path to reduce runtime allocations and GC pressure during large test suite executions.

Changes:

  • Replaced several LINQ-based filter/property matching paths with procedural switch + index-based loops.
  • Changed OperatorExpression.SubExpressions to an array to enable allocation-free indexed iteration.
  • Avoided substring allocations in path matching by slicing the input path into fragments.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/TreeNodeFilter.cs Reworks filter matching to reduce allocations (procedural loops, span-based fragment slicing).
src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/OperatorExpression.cs Stores sub-expressions as an array to enable efficient indexed iteration.

@abdelghani-moussaid
Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Copilot AI review requested due to automatic review settings May 5, 2026 22:23
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

Comment on lines 551 to 565
private static bool MatchFilterPattern(
FilterExpression filterExpression,
string testNodeFullPath,
int startFragmentIndex,
int endFragmentIndex,
PropertyBag properties)
{
string str = testNodeFullPath[startFragmentIndex..endFragmentIndex];
return MatchFilterPattern(filterExpression, str, properties);
ReadOnlySpan<char> fragment = testNodeFullPath.AsSpan(startFragmentIndex, endFragmentIndex - startFragmentIndex);
return MatchFilterPattern(filterExpression, fragment, properties);
}

private static bool MatchFilterPattern(
FilterExpression filterExpression,
string testNodeFragment,
ReadOnlySpan<char> testNodeFragment,
PropertyBag properties)
Comment thread src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/TreeNodeFilter.cs Outdated
Comment on lines +620 to +627
case PropertyExpression { PropertyName: var propExpr, Value: var valueExpr }:
foreach (IProperty prop in properties)
{
if (IsMatchingProperty(prop, propExpr, valueExpr))
{
return true;
}
}
Comment on lines 7 to 12
internal sealed class OperatorExpression(FilterOperator op, IReadOnlyCollection<FilterExpression> subExpressions) : FilterExpression
{
public FilterOperator Op { get; } = op;

public IReadOnlyCollection<FilterExpression> SubExpressions { get; } = subExpressions;
public IReadOnlyList<FilterExpression> SubExpressions { get; } = [.. subExpressions];
}
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 5, 2026 22:58
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

{
if (_filters.Count == 0)
{
throw new InvalidOperationException("Filter parsed to zero segments.");
Comment on lines +620 to +624
case PropertyExpression { PropertyName: var propExpr, Value: var valueExpr }:
foreach (IProperty prop in properties)
{
if (IsMatchingProperty(prop, propExpr, valueExpr))
{
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 5, 2026 23:07
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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.

2 participants