Skip to content

perf(Charts): Reduce allocations and optimize hot paths in Chart control#384

Open
PaulAndersonS wants to merge 1 commit into
mainfrom
paulandersons/perf-chart-optimizations
Open

perf(Charts): Reduce allocations and optimize hot paths in Chart control#384
PaulAndersonS wants to merge 1 commit into
mainfrom
paulandersons/perf-chart-optimizations

Conversation

@PaulAndersonS
Copy link
Copy Markdown
Collaborator

Description of Change

This PR implements 10 targeted performance optimizations in the Chart control to reduce allocations and improve rendering performance in hot paths:

  1. Replace O(n²) IndexOf with Dictionary lookup (CategoryAxis.cs) — distinctXValues.IndexOf(val) inside loops was O(n) per call. A pre-built Dictionary<string, int> gives O(1) lookups.

  2. Cache Dictionary.Values.ToList() outside loops (CartesianChartArea.cs) — SideBySideSeriesPosition.Values.ToList()[i] was allocating a new list every iteration. Now cached once before the loop.

  3. Cache GetType() results (CartesianChartArea.cs) — Repeated stackingSeries.GetType() and .Name calls replaced with local variables to avoid repeated reflection.

  4. Replace List.Contains() with HashSet (CategoryAxis.cs) — groupingValues.Contains(xValues[j]) was O(n). A parallel HashSet<string> provides O(1) membership tests.

  5. Replace string concatenation with StringBuilder (CategoryAxis.cs) — String += in a loop in GetLabelContent replaced with StringBuilder to avoid repeated allocations.

  6. Manual loop instead of LINQ in stacking calculation (CartesianChartArea.cs) — The GetYValue method already uses an optimized manual loop pattern (verified existing implementation).

  7. Single-pass min/max calculation (ErrorBarSegment.cs) — Four separate LINQ iterations (Where().Min(), Where().Max()) replaced with a single loop computing all four values.

  8. Reuse list allocations in TrackballBehavior (ChartTrackballBehavior.cs) — new List<T>(...) on every mouse move replaced with clear-and-reuse pattern to reduce GC pressure.

  9. Use string.Concat instead of += (RangeColumnSeries.cs) — Avoids intermediate string allocation in tooltip text construction.

  10. Replace Cast<T>() with pattern matching (CategoryAxis.cs, CartesianChartArea.cs, ChartTrackballBehavior.cs) — Avoids allocating a LINQ enumerator wrapper; direct iteration with is not T pattern is allocation-free.

Performance Impact

These changes target allocation-heavy hot paths that execute during chart rendering, data grouping, trackball interaction, and tooltip display. The optimizations reduce:

  • Unnecessary list/string allocations per frame
  • O(n²) algorithms to O(n) with dictionary/hashset lookups
  • Repeated reflection calls via caching
  • LINQ enumerator allocations via direct iteration

Issues Fixed

N/A — Performance improvement, no functional changes.

…ndering performance

- Replace O(n²) IndexOf with Dictionary lookup in CategoryAxis
- Cache Dictionary.Values.ToList() outside loops in CartesianChartArea
- Cache GetType() results to avoid repeated reflection
- Use HashSet for O(1) lookups instead of List.Contains()
- Use StringBuilder for string concatenation in loops
- Replace LINQ Where().Sum() with manual loop in rendering path
- Single-pass min/max calculation in ErrorBarSegment
- Reuse list allocations in TrackballBehavior
- Use string.Concat instead of += for tooltip text
- Replace Cast<T>() with pattern matching to avoid enumerator allocation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant