Skip to content

Classify element names once in StaticHtmlRenderer - #70151

Open
davidfowl wants to merge 1 commit into
mainfrom
perf/components-html-element-kind
Open

davidfowl wants to merge 1 commit into
mainfrom
perf/components-html-element-kind

Conversation

@davidfowl

Copy link
Copy Markdown
Member

StaticHtmlRenderer.RenderElement did several case-insensitive comparisons for every element it wrote:

  • textarea, form, option, select and script were each checked with string.Equals(..., OrdinalIgnoreCase);
  • a lookup in a HashSet<string>(OrdinalIgnoreCase) of self-closing elements, which hashes the name case-insensitively.

Description

  • Classify the element name once with a length-based switch into a small ElementKind enum. The comparisons are still OrdinalIgnoreCase, but most names are rejected by length alone.
  • Remove the SelfClosingElements HashSet. The same 14 elements are matched in the switch.
  • Read frame.ElementName into a local, and merge adjacent literal writes: '=' + '"' becomes "=\"", and ' ' + "action" + '=' + '"' becomes " action=\"".

The output is unchanged.

Benchmarks

I added HtmlRendererBenchmark in src/Components/Components/perf. It renders a 100-row table whose rows are child components, each with attributes, a checkbox and a link. The perf project now references Microsoft.AspNetCore.Components.Web.

Local BenchmarkDotNet, InProcess, 3 interleaved rounds against main:

Method main PR Δ
WriteHtmlOnly (HTML writing only, to a StringWriter) 30.36 / 30.45 / 31.14 µs 27.71 / 28.01 / 27.85 µs −9%
RenderAndWriteHtml (new renderer, render, then write) 106.5 / 118.6 / 119.1 µs 106.0 / 106.2 / 106.9 µs noisy, ≤ main

Allocations are unchanged: 128 B and 144,760 B.

Tests

  • Microsoft.AspNetCore.Components.Web.Tests: 330 passed, which includes the HtmlRenderer tests covering self-closing elements, textarea, select/option, form action and script.
  • Microsoft.AspNetCore.Components.Endpoints.Tests: 883 passed.

🤖 Posted by ASP.NET Core Perf 2 (AI Agent)

RenderElement compared every element name case-insensitively against
textarea, form, option, select and script, and looked it up in a HashSet of
self-closing elements. Replace those with a single length-based dispatch that
returns an ElementKind, and merge a few adjacent literal writes.

Adds HtmlRendererBenchmark.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 31, 2026 00:36
@davidfowl
davidfowl requested a review from a team as a code owner October 31, 2026 00:36
@github-actions github-actions Bot added the area-blazor Includes: Blazor, Razor Components label Oct 31, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The required frozen review bundle and existing-feedback snapshot are missing, preventing verification of revision provenance and feedback deduplication.

No open issues 🎉

What changed in this PR

Optimizes static HTML rendering by classifying element names once, preserving existing case-insensitive handling and HTML output.

Changes:

  • Replaces repeated element-name checks and the self-closing-element lookup with a length-based classifier.
  • Combines adjacent literal writes for attributes and form actions.
  • Adds benchmarks for HTML writing alone and rendering plus writing, with the required Components.Web project reference.
File Description
src/​Components/​Web/​src/​HtmlRendering/​StaticHtmlRenderer.HtmlWriting.cs Adds element classification and combines literal writes.
src/​Components/​Components/​perf/​Microsoft.AspNetCore.Components.Performance.csproj References Components.Web for renderer benchmarks.
src/​Components/​Components/​perf/​HtmlRendererBenchmark.cs Adds two benchmarks using a 100-row component table.

Review effort: Balanced

Copy link
Copy Markdown
Member Author

Compliance check: no public API changes (ElementKind is a private enum). The output is identical. All element checks still use StringComparison.OrdinalIgnoreCase, and the length switch covers exactly the same 14 self-closing elements as the removed HashSet. The merged literal writes (=\", action=\") produce the same characters.

🤖 Posted by ASP.NET Core Perf 2 (AI Agent)

@PureWeen

PureWeen commented Nov 1, 2026

Copy link
Copy Markdown
Member

/review

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-blazor Includes: Blazor, Razor Components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants