Skip to content

perf(model): read resetPropertiesStructKeyCase once per properties() call - #4632

Merged
bpamiri merged 2 commits into
developfrom
perf/settings-properties-hoist
Oct 23, 2026
Merged

bpamiri merged 2 commits into
developfrom
perf/settings-properties-hoist

Conversation

@bpamiri

@bpamiri bpamiri commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Part C of the approved performance slice. properties() read $get("resetPropertiesStructKeyCase") once per property inside its loop; it now reads the setting once at the top of the call.

 		local.rv = {};
 		local.propNames = propertyNames();
+		local.resetPropertyKeyCase = $get("resetPropertiesStructKeyCase");
 		for (local.key in this) {
 			...
-			if ($get("resetPropertiesStructKeyCase")) {
+			if (local.resetPropertyKeyCase) {

The flag is a call-local (local.resetPropertyKeyCase), exactly as the brief §C.2 prescribes — not an instance slot. That matters for correctness: model instances can be shared or long-lived, $get() honours per-tenant overrides, and a variables-scope flag would let two concurrent properties() calls on the same instance read each other's value mid-loop, and let a callback that re-enters properties() overwrite the outer loop's flag. A local has neither problem.

Semantics (unchanged)

  • One read per properties() call; a set() between two calls decides the casing of the next call's keys.
  • No cache and no invalidation surface — a deliberately rejected request-scoped $get memo (brief §C.3) is not used.
  • No public function/argument changes, no engine branch, no changelog fragment (brief says none).

Regression test

Added to propertiesSpec.cfc: "reads resetPropertiesStructKeyCase once per call, so a set between calls is visible". It toggles set(resetPropertiesStructKeyCase = false) then true between two properties() calls and asserts the returned key spelling follows — the raw this-scope spelling when off, the canonical propertyNames() spelling when on. Both spellings are read from the engine at runtime so the test is valid on Adobe (upper-case this scope) and Lucee (lower-case), and it guards against any future per-request memo.

Measured impact

The microbench is small and inconclusive on this host; I am reporting it as such rather than claiming a win:

  • $get("environment") costs ~1.6 µs/call on RustCFML (in-model and external), so removing 11–12 calls per properties() call is ~18 µs of work.
  • A same-server A/B of old vs hoisted methods showed differences on the order of a few percent on RustCFML and within noise on Lucee (~80 µs baseline), but host variance exceeded the effect and the runs were not a rigorous multi-run, both-engine sample — so no number is claimed as a win.
  • An early draft kept the flag in the variables scope; it was replaced with the brief's call-local for the thread/tenant and recursion reasons above. Correctness beats a sub-µs read, and the PR's own CI is the authoritative check.

Verification run on this change (head 7a25a6bc6)

  • bash tools/test-local.sh model → 1502 passed (includes the regression test, green)
  • bash tools/test-local.sh database → 189 passed
  • tools/test-matrix.sh lucee7 sqlite passed during development (full suite); PR CI re-runs the engine matrix on this head.

Part C of the approved performance slice; PR A (#4631) is merged, PR B was dropped after its premise measured as a regression.

…call

properties() called $get("resetPropertiesStructKeyCase") once per property
inside its loop. Read it once at the top of the call instead. The flag is
kept in the variables scope rather than a new local: on RustCFML a local
assigned before `for (local.key in this)` measured slower in local runs,
while this slot is overwritten on every call and never read across calls.

Semantics are unchanged: one read per properties() call, a set() between
calls decides the next call's key casing (covered by a new regression
test), no cache or invalidation surface, and no public API change.

Part of the approved performance slice (C: properties() settings hoist).

Signed-off-by: Peter Amiri <peter@alurium.com>
The previous commit read the setting once but stored the flag in the
variables scope, so two concurrent properties() calls on a shared model
instance could read each other's value mid-loop (tenant config resolves
through $get()), and a callback that re-enters properties() could
overwrite the outer loop's flag. Keep the single read, but hold it in a
local as the brief prescribes: one read per call, a set() between calls
still decides the next call's casing, no instance state.

Part of the approved performance slice (C: properties() settings hoist).

Signed-off-by: Peter Amiri <peter@alurium.com>
@bpamiri
bpamiri merged commit 2b1a8a6 into develop Oct 23, 2026
23 checks passed
@bpamiri
bpamiri deleted the perf/settings-properties-hoist branch October 23, 2026 00:15
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