Skip to content

perf: fix ParseWorkflow regression (+29% → -51%) via targeted caching#44994

Draft
pelikhan with Copilot wants to merge 4 commits into
mainfrom
copilot/fix-parseworkflow-performance
Draft

perf: fix ParseWorkflow regression (+29% → -51%) via targeted caching#44994
pelikhan with Copilot wants to merge 4 commits into
mainfrom
copilot/fix-parseworkflow-performance

Conversation

Copilot AI commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

ParseWorkflow regressed from 371μs to 479μs (+29.2%) due to three per-call allocations that were unnecessary for the common case (no imports, no custom model aliases).

Root causes & fixes

MergeImportedModelAliases — 5KB deep copy on every call (model_aliases.go)

  • 52-entry builtin map (154 string slices) was deep-copied on every ParseWorkflowFile invocation, even when importedModels and frontmatterModels were both empty (the overwhelmingly common case)
  • Added getBuiltinOnlyAliasMap() returning a shared read-only singleton; MergeImportedModelAliases returns it directly when both inputs are empty
  • Added mapHeaderPointer() via unsafe.Pointer + isBuiltinOnlyAliasMap() for zero-overhead map identity comparison (Go disallows map == map)

detectCircularModelAliases — 52-node DFS on every call (model_alias_validation.go)

  • Full DFS + sorted-key allocation over all 52 builtin aliases ran unconditionally on every parse, even though builtins never change
  • Cached via builtinCycleCheckOnce; runs at most once per process lifetime when the map is the shared builtin

NewTools — 14-entry map literal allocated per call (tools_parser.go)

  • knownTools map literal was re-created inside NewTools() on every invocation
  • Promoted to a package-level variable

Template validation — regex executed on all markdown (template_validation.go)

  • Three validation functions ran FindAllStringSubmatch unconditionally
  • Added strings.Contains("{{#if") / strings.Contains("{{#") fast-paths to skip regex when no template blocks are present

Result

ns/op
Historical baseline 371,023
Regression (reported) 479,440
After this PR (local) ~240,000

~50% faster than the regression; ~35% faster than the historical baseline.

Copilot AI and others added 3 commits July 11, 2026 18:00
- model_aliases.go: add isBuiltinOnlyAliasMap() using reflect.ValueOf().Pointer()
  for map identity check, plus builtinOnlyAliasMapID uintptr cache variable;
  MergeImportedModelAliases returns shared builtin map (no deep copy) when no
  imports/overrides present - avoids 154 string slice allocations per call

- model_alias_validation.go: detectCircularModelAliases() fast-path caches DFS
  result for builtin-only map via builtinCycleCheckOnce (fixes invalid map==map
  comparison by using isBuiltinOnlyAliasMap() instead)

- tools_parser.go: promote knownTools from local map literal to package-level
  variable to avoid 14-entry map allocation on every NewTools() call

- template_validation.go: add strings.Contains fast-paths to three validation
  functions so they skip regex entirely for markdown with no template blocks

Benchmark: 479μs (regression) → 235μs (local, -51%)

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
… cached error path

- model_aliases.go: replace reflect.ValueOf().Pointer() with mapHeaderPointer()
  using unsafe.Pointer extraction; avoids reflection overhead on every call;
  remove redundant getBuiltinOnlyAliasMap() call from isBuiltinOnlyAliasMap()

- model_alias_validation.go: pass empty path to builtin cycle check so a
  hypothetical cached error does not expose a caller's workflow path

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
- model_aliases.go: expand mapHeaderPointer comment with Go version stability
  note; expand MergeImportedModelAliases doc with prominent immutability warning
  using ⚠ symbol

- model_alias_validation.go: use sentinel '<builtin-aliases>' path in builtin
  cycle check instead of empty string for clearer hypothetical error messages

- template_validation.go: add comment explaining why '{{#if' is the correct
  fast-path substring (templateRegionPattern exclusively matches {{#if blocks)

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Triage

Field Value
Category refactor (perf)
Risk 🟡 Medium
Score 62/100 (Impact 32 + Urgency 15 + Quality 15)
Action 🔄 batch_review
CI ⚪ Unknown (draft, no checks yet)
Batch perf-regressions (with #44995, #44996)

Draft PR — largest of the perf batch (132 additions, 4 files). Unmark draft and ensure CI passes before merging.

Generated by 🔧 PR Triage Agent · 180.8 AIC · ⌖ 7.62 AIC · ⊞ 5.6K ·

@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Triage

Field Value
Category refactor / perf
Risk 🟡 medium
Score 50/100 (impact:25 + urgency:15 + quality:10)
Action 📦 batch_review
Batch pr-batch:perf-regressions (with #44995, #44996)

Rationale: Draft. Fixes ParseWorkflow +29% regression via targeted caching. Good approach; batch with other perf PRs for efficient review.

Triage run §29183606049

Generated by 🔧 PR Triage Agent · 171.6 AIC · ⌖ 5.63 AIC · ⊞ 5.6K ·

@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Triage

Field Value
Category refactor (perf)
Risk 🟡 Medium
Score 55/100 (impact: 25, urgency: 16, quality: 14)
Batch perf-regressions (with #44995, #44996)
Action batch_review

Draft. ParseWorkflow caching improvement. +132/-20. Review in perf-regressions batch.

Generated by 🔧 PR Triage Agent · 48.3 AIC · ⌖ 7.99 AIC · ⊞ 5.6K ·

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.

[performance] Regression in ParseWorkflow: +29.2% slower

2 participants