Skip to content

perf(helpers): memoize acf_get_fields() per group - #156

Merged
parisek merged 1 commit into
mainfrom
perf/memoize-group-fields
Aug 27, 2026
Merged

perf(helpers): memoize acf_get_fields() per group#156
parisek merged 1 commit into
mainfrom
perf/memoize-group-fields

Conversation

@parisek

@parisek parisek commented Aug 27, 2026

Copy link
Copy Markdown
Owner

From which project

sloneek, from the profiling that produced #154. Stacked on #154 — the base is perf/memoize-capability-probes, so this diff shows only the new change; GitHub retargets to main when #154 merges.

The finding

getFieldObjectsByScreen() asks ACF for a group's fields once per group per screen. A 90-item menu resolves the same group ninety times.

calls time
before 348 26-38 ms
after 21 5-8 ms

That is measured on the work removed. End to end the page moved 16-34 ms across two rounds — the same saving through a noisier instrument, and the machine drifted between them, so the component figure is the one to trust.

Rendered HTML is byte-identical, checked against a control pair of runs of the unchanged code because the page is not deterministic without one.

Why this one needs no invalidation

Field definitions are configuration, not content. They come from the theme's JSON, they do not depend on the page being rendered, and nothing a visitor does changes them. The memo lives for the request, and there is nothing for a hook to notice.

A field value is the opposite, which is exactly why #155 is parked: caching one layer up meant caching formatted output, and formatting runs shortcodes and arbitrary filters that can read the current page. The two look like the same optimisation and are not.

The key carries the blog id and the language for the same reasons the screen memo's does — groups are registered per site, ACFML translates a field's label, instructions and choices. A group carrying neither a key nor an id has no identity to memoize on and is asked every time, rather than sharing an entry with the next anonymous group.

What was tried and did not work

Recorded so nobody repeats it. All measured on the front page, all rejected:

Idea Result
Prime the post-meta cache for all menu items Already warm. 0 ms, query count unchanged.
Skip get_field() where the item has no stored meta 206 of 272 calls skipped, time unchanged — ACF already short-circuits.
Prime the term-relationship cache Already warm, and added 5 queries.
Cache the formatted field payload #155, parked: formatting is not a function of the stored value.

What remains unclaimed on this path is get_field() for the 66 menu-item fields that hold a value (37-68 ms) and wp_get_post_terms() (9-16 ms, removable only by threading the known menu id through formatFields()).

Tests

Nine, each verified to fail without the memo where it is the mechanism under test: one group asked once, identity by key and by ID, an anonymous group asked every time, separation by language and by blog, a non-array answer normalized, and the flush.

@parisek parisek self-assigned this Aug 27, 2026
Base automatically changed from perf/memoize-capability-probes to main August 27, 2026 12:34
`getFieldObjectsByScreen()` asked ACF for a group's fields once per group
per screen. On the sloneek front page that is 348 calls producing 21
distinct answers, because a 90-item menu resolves the same group ninety
times.

Measured on the work removed rather than on the page: 26-38 ms falls to
5-8 ms. End to end the page moved 16-34 ms across two rounds; the machine
drifted between them, so the direct measurement is the one to trust.

**This needs no invalidation, and that is the point.** Field definitions
are configuration — theme JSON, not anything a visitor changes — so the
memo lives for the request and nothing has to notice when it should die. A
field *value* is the opposite, which is why the parked #155 could not do
the same thing one layer up.

The key carries the blog id and the language for the reasons the screen
memo's does: groups are registered per site, and ACFML translates a field's
label, instructions and choices. A group carrying neither a key nor an id
has no identity and is asked every time, rather than sharing one entry with
the next anonymous group.

Output is byte-identical against a control pair of runs.
@parisek
parisek force-pushed the perf/memoize-group-fields branch from 594d6f2 to 189e81e Compare August 27, 2026 12:35
@parisek
parisek merged commit cc5f6f7 into main Aug 27, 2026
6 checks passed
@parisek
parisek deleted the perf/memoize-group-fields branch August 27, 2026 12:37
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