Skip to content

perf: stop rebuilding the center html panel event on every tick - #45

Open
makadore wants to merge 1 commit into
schwarper:mainfrom
makadore:perf/center-html-menu
Open

makadore wants to merge 1 commit into
schwarper:mainfrom
makadore:perf/center-html-menu

Conversation

@makadore

Copy link
Copy Markdown

CenterHtmlMenuInstance.Display is registered as an OnTick listener, and the panel only stays on screen while it is being resent, so an open menu goes through this path 64 times a second for every player looking at it.

PrintToCenterHtml creates a game event, fires it and frees it. Doing that on every tick is most of what an open menu costs. The event is kept for as long as the menu is and refilled with the new markup instead, then freed in Close.

Measurement

Taken on a live CS2 server with a 12 item menu, by alternating the two send paths on every other render so both meet the same conditions rather than running one after the other:

path sends per send
create and free each tick 2466 36.57us
keep and refill 2466 27.54us

The same change in CounterStrikeSharp's own CenterHtmlMenu measured 36.81us against 29.02us, which is where the confidence in this number comes from — two separate code bases, same mechanism, same size of gain.

The smaller half

AddPageOptions asks the localizer for Prev, Exit and Next on every render, unconditionally, even when those buttons are not shown. Each of those calls goes through ReplaceColorTags, which reflects over ChatColors. Benchmarked on its own that is 0.324us a call, so about 1us of the 37 — real but not the main cost. The labels are read once when the menu is opened instead, and one StringBuilder is reused rather than allocated per render.

Colours are still read on every render, since a plugin can change CenterHtmlMenu_PrevPageColor and friends after the menu is built.

One behaviour note

Sending returns early when the player is no longer valid rather than throwing, which is what PrintToCenterHtml does through an internal guard that is not reachable from here. For something that runs every tick that seemed the better of the two, and Close already checks Player.IsValid the same way.

Checked in game: menu opens, pages turn, items select, closes on select and on !0.

Display is registered as an OnTick listener and the panel only stays on
screen while it is being resent, so an open menu goes through this path 64
times a second per player. PrintToCenterHtml creates a game event, fires it
and frees it, and doing that each time is most of what an open menu costs.
The event is kept for as long as the menu is and refilled with the new
markup instead, then freed in Close.

Measured on a live server with a 12 item menu by alternating the two paths
on every other render, so both meet the same conditions: 36.57us over 2466
sends against 27.54us over 2466.

The labels and the builder go with it: AddPageOptions asks the localizer
for Prev, Exit and Next unconditionally on every render, even when the
buttons are not shown, and each of those runs ReplaceColorTags, which
reflects over ChatColors. That is about 1us of the 37, so it is the small
half of this, but none of it changes while the menu is open. Colours are
still read per render, since a plugin can change them after the menu is
built.

Sending now returns early on an invalid player rather than throwing the way
PrintToCenterHtml does, which suits something that runs every tick; Close
already guarded the same way.
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