[go] text/template: avoid reflect.Value.Call for common builtins

9 views
Skip to first unread message

Zhandos Zhandarbek (Gerrit)

unread,
Sep 24, 2026, 12:42:37 AM (10 days ago) Sep 24
to Alan Donovan, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
Attention needed from Alan Donovan and Sean Liao

Zhandos Zhandarbek has uploaded the change for review

Zhandos Zhandarbek would like Sean Liao and Alan Donovan to review this change.

Commit message

text/template: avoid reflect.Value.Call for common builtins

CL 835445 added a fast path in safeCall for func(...any) string. The
other builtins still go through reflect.Value.Call: eq, ne, lt, le, gt,
ge, index, slice, len, not and printf. Comparisons and index appear in
almost every real template, typically as {{if eq .X "y"}}.

Check for the signatures of these builtins in safeCall and call the
function directly. Arguments of type reflect.Value are unwrapped with
reflect.TypeAssert, which does not allocate. User-defined functions
with the same signatures take the same path. The checks sit inside the
existing recover, so a panic is still reported as an error. Functions
with other signatures are unaffected (userFunc below).

Old and new binaries run alternately, 10 runs each, on an Apple M4 Pro:

pkg: text/template
│ old.txt │ new.txt │
│ sec/op │ sec/op vs base │
ExecuteBuiltins/eq-12 548.2n ± 2% 286.4n ± 2% -47.77% (p=0.000 n=10)
ExecuteBuiltins/eqMulti-12 666.2n ± 2% 391.0n ± 4% -41.32% (p=0.000 n=10)
ExecuteBuiltins/lt-12 476.1n ± 2% 262.4n ± 3% -44.88% (p=0.000 n=10)
ExecuteBuiltins/not-12 362.1n ± 2% 210.0n ± 2% -42.00% (p=0.000 n=10)
ExecuteBuiltins/len-12 434.8n ± 3% 245.6n ± 3% -43.53% (p=0.000 n=10)
ExecuteBuiltins/index-12 617.6n ± 3% 356.4n ± 5% -42.31% (p=0.000 n=10)
ExecuteBuiltins/printf-12 655.4n ± 3% 439.9n ± 4% -32.88% (p=0.000 n=10)

│ old.txt │ new.txt │
│ allocs/op │ allocs/op vs base │
ExecuteBuiltins/eq-12 14.000 ± 0% 9.000 ± 0% -35.71% (p=0.000 n=10)
ExecuteBuiltins/eqMulti-12 18.00 ± 0% 13.00 ± 0% -27.78% (p=0.000 n=10)
ExecuteBuiltins/lt-12 10.000 ± 0% 7.000 ± 0% -30.00% (p=0.000 n=10)
ExecuteBuiltins/not-12 7.000 ± 0% 5.000 ± 0% -28.57% (p=0.000 n=10)
ExecuteBuiltins/len-12 8.000 ± 0% 5.000 ± 0% -37.50% (p=0.000 n=10)
ExecuteBuiltins/index-12 15.00 ± 0% 11.00 ± 0% -26.67% (p=0.000 n=10)
ExecuteBuiltins/printf-12 14.00 ± 0% 11.00 ± 0% -21.43% (p=0.000 n=10)

pkg: text/template
│ old.txt │ new.txt │
│ sec/op │ sec/op vs base │
ExecuteBuiltins/userFunc-12 372.7n ± 2% 371.6n ± 3% ~ (p=0.955 n=10)

Updates #81610
Change-Id: Ic0cbe2fb8b5770699083321e90113e07f35a3c68

Change diff

diff --git a/src/text/template/exec_test.go b/src/text/template/exec_test.go
index 48895db..53359c6 100644
--- a/src/text/template/exec_test.go
+++ b/src/text/template/exec_test.go
@@ -1782,6 +1782,12 @@
"doPanicVariadic": func(...any) string {
panic("custom panic string")
},
+ "doPanicCompare": func(reflect.Value, reflect.Value) (bool, error) {
+ panic("custom panic string")
+ },
+ "doPanicFormat": func(string, ...any) string {
+ panic("custom panic string")
+ },
}
tests := []struct {
name string
@@ -1810,6 +1816,16 @@
`template: t:1:6: executing "t" at <doPanicVariadic>: error calling doPanicVariadic: custom panic string`,
},
{
+ "compare-like func call panics",
+ "{{doPanicCompare 1 2}}", (*T)(nil),
+ `template: t:1:2: executing "t" at <doPanicCompare 1 2>: error calling doPanicCompare: custom panic string`,
+ },
+ {
+ "printf-like func call panics",
+ `{{2 | doPanicFormat "%d"}}`, (*T)(nil),
+ `template: t:1:6: executing "t" at <doPanicFormat "%d">: error calling doPanicFormat: custom panic string`,
+ },
+ {
"direct method call panics",
"{{.GetU}}", (*T)(nil),
`template: t:1:2: executing "t" at <.GetU>: error calling GetU: runtime error: invalid memory address or nil pointer dereference`,
@@ -2043,3 +2059,90 @@
}
}
}
+
+func BenchmarkExecuteBuiltins(b *testing.B) {
+ data := map[string]any{
+ "S": "active",
+ "N": 3,
+ "L": []int{1, 2, 3},
+ "M": map[string]string{"k": "v"},
+ }
+ tmpls := []struct{ name, text string }{
+ {"eq", `{{if eq .S "active"}}y{{end}}`},
+ {"eqMulti", `{{if eq .S "a" "b" "active"}}y{{end}}`},
+ {"lt", `{{if lt .N 5}}y{{end}}`},
+ {"not", `{{if not .S}}y{{end}}`},
+ {"len", `{{len .L}}`},
+ {"index", `{{index .M "k"}}`},
+ {"printf", `{{printf "%s-%d" .S .N}}`},
+ {"userFunc", `{{double .N}}`},
+ }
+ for _, tt := range tmpls {
+ tmpl := Must(New("t").Funcs(FuncMap{"double": func(n int) int { return 2 * n }}).Parse(tt.text))
+ b.Run(tt.name, func(b *testing.B) {
+ b.ReportAllocs()
+ for b.Loop() {
+ if err := tmpl.Execute(io.Discard, data); err != nil {
+ b.Fatal(err)
+ }
+ }
+ })
+ }
+}
+
+func TestExecuteBuiltinSignatureFuncs(t *testing.T) {
+ funcs := FuncMap{
+ "same": func(a, b reflect.Value) (bool, error) {
+ if a.Kind() != b.Kind() {
+ return false, fmt.Errorf("kinds differ: %s, %s", a.Kind(), b.Kind())
+ }
+ return a.Interface() == b.Interface(), nil
+ },
+ "anyOf": func(x reflect.Value, ys ...reflect.Value) (bool, error) {
+ for _, y := range ys {
+ if x.Interface() == y.Interface() {
+ return true, nil
+ }
+ }
+ return false, nil
+ },
+ "first": func(x reflect.Value, _ ...reflect.Value) (reflect.Value, error) {
+ return x.Index(0), nil
+ },
+ "size": func(x reflect.Value) (int, error) { return x.Len(), nil },
+ "isStr": func(x reflect.Value) bool { return x.Kind() == reflect.String },
+ "fmt": func(format string, args ...any) string { return fmt.Sprintf(format, args...) },
+ }
+ tests := []struct {
+ input, want, wantErr string
+ }{
+ {`{{same 1 1}} {{same "a" "b"}} {{1 | same 1}}`, "true false true", ""},
+ {`{{same 1 "a"}}`, "", "error calling same: kinds differ: int, string"},
+ {`{{anyOf 3 1 2 3}} {{anyOf 3}} {{3 | anyOf 1 2}}`, "true false false", ""},
+ {`{{first .L}} {{.L | first}}`, "x x", ""},
+ {`{{size .L}} {{.L | size}}`, "2 2", ""},
+ {`{{isStr "a"}} {{isStr 1}} {{"a" | isStr}}`, "true false true", ""},
+ {`{{same .RV "s"}} {{isStr .RV}} {{size .RV}}`, "true true 1", ""},
+ {`{{eq nil nil}} {{not nil}}`, "true true", ""},
+ {`{{fmt "%s=%d" "a" 1}} {{1 | fmt "%d"}} {{"%%" | fmt}}`, "a=1 1 %", ""},
+ }
+ for _, tt := range tests {
+ tmpl := Must(New("t").Funcs(funcs).Parse(tt.input))
+ var b strings.Builder
+ err := tmpl.Execute(&b, struct {
+ L []string
+ RV reflect.Value
+ }{[]string{"x", "y"}, reflect.ValueOf("s")})
+ if tt.wantErr != "" {
+ if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
+ t.Errorf("%s: got error %v, want %q", tt.input, err, tt.wantErr)
+ }
+ continue
+ }
+ if err != nil {
+ t.Errorf("%s: unexpected error: %v", tt.input, err)
+ } else if b.String() != tt.want {
+ t.Errorf("%s: got %q, want %q", tt.input, b.String(), tt.want)
+ }
+ }
+}
diff --git a/src/text/template/funcs.go b/src/text/template/funcs.go
index 9985eab..9e2fb71 100644
--- a/src/text/template/funcs.go
+++ b/src/text/template/funcs.go
@@ -373,6 +373,33 @@
}
return reflect.ValueOf(f(anyArgs...)), nil
}
+ // Fast paths for the signatures of the other builtins.
+ if f, ok := reflect.TypeAssert[func(reflect.Value, reflect.Value) (bool, error)](fun); ok {
+ r, err := f(unwrapArg(args[0]), unwrapArg(args[1]))
+ return reflect.ValueOf(r), err
+ }
+ if f, ok := reflect.TypeAssert[func(reflect.Value, ...reflect.Value) (bool, error)](fun); ok {
+ r, err := f(unwrapArg(args[0]), unwrapArgs(args[1:])...)
+ return reflect.ValueOf(r), err
+ }
+ if f, ok := reflect.TypeAssert[func(reflect.Value, ...reflect.Value) (reflect.Value, error)](fun); ok {
+ r, err := f(unwrapArg(args[0]), unwrapArgs(args[1:])...)
+ return reflect.ValueOf(r), err
+ }
+ if f, ok := reflect.TypeAssert[func(reflect.Value) (int, error)](fun); ok {
+ r, err := f(unwrapArg(args[0]))
+ return reflect.ValueOf(r), err
+ }
+ if f, ok := reflect.TypeAssert[func(reflect.Value) bool](fun); ok {
+ return reflect.ValueOf(f(unwrapArg(args[0]))), nil
+ }
+ if f, ok := reflect.TypeAssert[func(string, ...any) string](fun); ok {
+ anyArgs := make([]any, len(args)-1)
+ for i, arg := range args[1:] {
+ anyArgs[i] = arg.Interface()
+ }
+ return reflect.ValueOf(f(args[0].String(), anyArgs...)), nil
+ }
ret := fun.Call(args)
if len(ret) == 2 && !ret[1].IsNil() {
return ret[0], ret[1].Interface().(error)
@@ -380,6 +407,24 @@
return ret[0], nil
}

+// unwrapArg returns the reflect.Value that evalCall wraps in another
+// reflect.Value for parameters of type reflect.Value.
+func unwrapArg(arg reflect.Value) reflect.Value {
+ v, ok := reflect.TypeAssert[reflect.Value](arg)
+ if !ok {
+ panic("text/template: argument is not a wrapped reflect.Value")
+ }
+ return v
+}
+
+func unwrapArgs(args []reflect.Value) []reflect.Value {
+ vs := make([]reflect.Value, len(args))
+ for i, arg := range args {
+ vs[i] = unwrapArg(arg)
+ }
+ return vs
+}
+
// Boolean logic.

func truth(arg reflect.Value) bool {

Change information

Files:
  • M src/text/template/exec_test.go
  • M src/text/template/funcs.go
Change size: M
Delta: 2 files changed, 148 insertions(+), 0 deletions(-)
Open in Gerrit

Related details

Attention is currently required from:
  • Alan Donovan
  • Sean Liao
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newchange
Gerrit-Project: go
Gerrit-Branch: master
Gerrit-Change-Id: Ic0cbe2fb8b5770699083321e90113e07f35a3c68
Gerrit-Change-Number: 837945
Gerrit-PatchSet: 1
Gerrit-Owner: Zhandos Zhandarbek <zhandos.zh...@gmail.com>
Gerrit-Reviewer: Alan Donovan <adon...@google.com>
Gerrit-Reviewer: Sean Liao <se...@liao.dev>
Gerrit-Attention: Sean Liao <se...@liao.dev>
Gerrit-Attention: Alan Donovan <adon...@google.com>
unsatisfied_requirement
satisfied_requirement
open
diffy

Gopher Robot (Gerrit)

unread,
Sep 24, 2026, 12:45:59 AM (10 days ago) Sep 24
to Zhandos Zhandarbek, goph...@pubsubhelper.golang.org, Alan Donovan, golang-co...@googlegroups.com
Attention needed from Alan Donovan and Sean Liao

Message from Gopher Robot

Congratulations on opening your first change. Thank you for your contribution!

Next steps:
A maintainer will review your change and provide feedback. See
https://go.dev/doc/contribute#review for more info and tips to get your
patch through code review.

Most changes in the Go project go through a few rounds of revision. This can be
surprising to people new to the project. The careful, iterative review process
is our way of helping mentor contributors and ensuring that their contributions
have a lasting impact.

During May-July and Nov-Jan the Go project is in a code freeze, during which
little code gets reviewed or merged. If a reviewer responds with a comment like
R=go1.11 or adds a tag like "wait-release", it means that this CL will be
reviewed as part of the next development cycle. See https://go.dev/s/release
for more details.

Open in Gerrit

Related details

Attention is currently required from:
  • Alan Donovan
  • Sean Liao
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: go
Gerrit-Branch: master
Gerrit-Change-Id: Ic0cbe2fb8b5770699083321e90113e07f35a3c68
Gerrit-Change-Number: 837945
Gerrit-PatchSet: 1
Gerrit-Owner: Zhandos Zhandarbek <zhandos.zh...@gmail.com>
Gerrit-Reviewer: Alan Donovan <adon...@google.com>
Gerrit-Reviewer: Sean Liao <se...@liao.dev>
Gerrit-CC: Gopher Robot <go...@golang.org>
Gerrit-Attention: Sean Liao <se...@liao.dev>
Gerrit-Attention: Alan Donovan <adon...@google.com>
Gerrit-Comment-Date: Thu, 24 Sep 2026 04:45:51 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: No
unsatisfied_requirement
satisfied_requirement
open
diffy

Zhandos Zhandarbek (Gerrit)

unread,
Sep 24, 2026, 1:08:31 AM (10 days ago) Sep 24
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
Attention needed from Alan Donovan and Sean Liao

Zhandos Zhandarbek uploaded new patchset

Zhandos Zhandarbek uploaded patch set #2 to this change.
Open in Gerrit

Related details

Attention is currently required from:
  • Alan Donovan
  • Sean Liao
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newpatchset
Gerrit-Project: go
Gerrit-Branch: master
Gerrit-Change-Id: Ic0cbe2fb8b5770699083321e90113e07f35a3c68
Gerrit-Change-Number: 837945
Gerrit-PatchSet: 2
unsatisfied_requirement
satisfied_requirement
open
diffy

Zhandos Zhandarbek (Gerrit)

unread,
Sep 24, 2026, 1:10:27 AM (10 days ago) Sep 24
to goph...@pubsubhelper.golang.org, Gopher Robot, Alan Donovan, golang-co...@googlegroups.com
Attention needed from Alan Donovan and Sean Liao

Zhandos Zhandarbek added 1 comment

Patchset-level comments
File-level comment, Patchset 2 (Latest):
Zhandos Zhandarbek . resolved

PS2: added an html/template page benchmark (-37% time, -38% allocs) and switched to a type switch so functions with other signatures are not slowed down. PS1 was measured against the wrong baseline and missed a ~3% overhead there.

Open in Gerrit

Related details

Attention is currently required from:
  • Alan Donovan
  • Sean Liao
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: go
Gerrit-Branch: master
Gerrit-Change-Id: Ic0cbe2fb8b5770699083321e90113e07f35a3c68
Gerrit-Change-Number: 837945
Gerrit-PatchSet: 2
Gerrit-Owner: Zhandos Zhandarbek <zhandos.zh...@gmail.com>
Gerrit-Reviewer: Alan Donovan <adon...@google.com>
Gerrit-Reviewer: Sean Liao <se...@liao.dev>
Gerrit-CC: Gopher Robot <go...@golang.org>
Gerrit-Attention: Sean Liao <se...@liao.dev>
Gerrit-Attention: Alan Donovan <adon...@google.com>
Gerrit-Comment-Date: Thu, 24 Sep 2026 05:10:18 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
unsatisfied_requirement
satisfied_requirement
open
diffy

Alan Donovan (Gerrit)

unread,
Sep 24, 2026, 5:31:35 PM (10 days ago) Sep 24
to Zhandos Zhandarbek, goph...@pubsubhelper.golang.org, Gopher Robot, golang-co...@googlegroups.com
Attention needed from Sean Liao and Zhandos Zhandarbek

Alan Donovan added 1 comment

Patchset-level comments
Alan Donovan . resolved

Are all of these needed? I would guess most builtins are pretty rarely used in production and not worth optimizing.

Open in Gerrit

Related details

Attention is currently required from:
  • Sean Liao
  • Zhandos Zhandarbek
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: go
Gerrit-Branch: master
Gerrit-Change-Id: Ic0cbe2fb8b5770699083321e90113e07f35a3c68
Gerrit-Change-Number: 837945
Gerrit-PatchSet: 2
Gerrit-Owner: Zhandos Zhandarbek <zhandos.zh...@gmail.com>
Gerrit-Reviewer: Alan Donovan <adon...@google.com>
Gerrit-Reviewer: Sean Liao <se...@liao.dev>
Gerrit-CC: Gopher Robot <go...@golang.org>
Gerrit-Attention: Sean Liao <se...@liao.dev>
Gerrit-Attention: Zhandos Zhandarbek <zhandos.zh...@gmail.com>
Gerrit-Comment-Date: Thu, 24 Sep 2026 21:31:29 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
unsatisfied_requirement
satisfied_requirement
open
diffy

Zhandos Zhandarbek (Gerrit)

unread,
Sep 25, 2026, 3:47:25 AM (9 days ago) Sep 25
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
Attention needed from Sean Liao and Zhandos Zhandarbek

Zhandos Zhandarbek uploaded new patchset

Zhandos Zhandarbek uploaded patch set #3 to this change.
Open in Gerrit

Related details

Attention is currently required from:
  • Sean Liao
  • Zhandos Zhandarbek
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newpatchset
Gerrit-Project: go
Gerrit-Branch: master
Gerrit-Change-Id: Ic0cbe2fb8b5770699083321e90113e07f35a3c68
Gerrit-Change-Number: 837945
Gerrit-PatchSet: 3
unsatisfied_requirement
satisfied_requirement
open
diffy

Zhandos Zhandarbek (Gerrit)

unread,
Sep 25, 2026, 3:51:52 AM (9 days ago) Sep 25
to goph...@pubsubhelper.golang.org, Gopher Robot, Alan Donovan, golang-co...@googlegroups.com
Attention needed from Alan Donovan and Sean Liao

Zhandos Zhandarbek added 1 comment

Patchset-level comments
Alan Donovan . resolved

Are all of these needed? I would guess most builtins are pretty rarely used in production and not worth optimizing.

Zhandos Zhandarbek

Fair enough. I grepped bitnami/charts (~3300 templates, plain
text/template + sprig) to see what's actually used:

  printf 2.5%, not 2.4%, eq 2.2%, ne 0.5% of all actions
index 0.1%, len/lt/le/gt/ge/slice under 0.1%

and/or are already handled separately in evalCall.

So I dropped index, slice and len in PS3. The page benchmark is still
-38%

Open in Gerrit

Related details

Attention is currently required from:
  • Alan Donovan
  • Sean Liao
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: go
Gerrit-Branch: master
Gerrit-Change-Id: Ic0cbe2fb8b5770699083321e90113e07f35a3c68
Gerrit-Change-Number: 837945
Gerrit-PatchSet: 3
Gerrit-Owner: Zhandos Zhandarbek <zhandos.zh...@gmail.com>
Gerrit-Reviewer: Alan Donovan <adon...@google.com>
Gerrit-Reviewer: Sean Liao <se...@liao.dev>
Gerrit-CC: Gopher Robot <go...@golang.org>
Gerrit-Attention: Sean Liao <se...@liao.dev>
Gerrit-Attention: Alan Donovan <adon...@google.com>
Gerrit-Comment-Date: Fri, 25 Sep 2026 07:51:40 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: Alan Donovan <adon...@google.com>
unsatisfied_requirement
satisfied_requirement
open
diffy

Alan Donovan (Gerrit)

unread,
Sep 25, 2026, 8:45:11 AM (9 days ago) Sep 25
to Zhandos Zhandarbek, goph...@pubsubhelper.golang.org, Gopher Robot, golang-co...@googlegroups.com
Attention needed from Sean Liao and Zhandos Zhandarbek

Alan Donovan voted Code-Review+2

Code-Review+2
Open in Gerrit

Related details

Attention is currently required from:
  • Sean Liao
  • Zhandos Zhandarbek
Submit Requirements:
  • requirement satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: go
Gerrit-Branch: master
Gerrit-Change-Id: Ic0cbe2fb8b5770699083321e90113e07f35a3c68
Gerrit-Change-Number: 837945
Gerrit-PatchSet: 3
Gerrit-Owner: Zhandos Zhandarbek <zhandos.zh...@gmail.com>
Gerrit-Reviewer: Alan Donovan <adon...@google.com>
Gerrit-Reviewer: Sean Liao <se...@liao.dev>
Gerrit-CC: Gopher Robot <go...@golang.org>
Gerrit-Attention: Sean Liao <se...@liao.dev>
Gerrit-Attention: Zhandos Zhandarbek <zhandos.zh...@gmail.com>
Gerrit-Comment-Date: Fri, 25 Sep 2026 12:45:05 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: Yes
satisfied_requirement
unsatisfied_requirement
open
diffy

Sean Liao (Gerrit)

unread,
Sep 25, 2026, 4:49:07 PM (9 days ago) Sep 25
to Zhandos Zhandarbek, goph...@pubsubhelper.golang.org, Alan Donovan, Gopher Robot, golang-co...@googlegroups.com
Attention needed from Zhandos Zhandarbek

Sean Liao voted

Auto-Submit+1
Code-Review+1
Commit-Queue+1
Open in Gerrit

Related details

Attention is currently required from:
  • Zhandos Zhandarbek
Submit Requirements:
  • requirement satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: go
Gerrit-Branch: master
Gerrit-Change-Id: Ic0cbe2fb8b5770699083321e90113e07f35a3c68
Gerrit-Change-Number: 837945
Gerrit-PatchSet: 3
Gerrit-Owner: Zhandos Zhandarbek <zhandos.zh...@gmail.com>
Gerrit-Reviewer: Alan Donovan <adon...@google.com>
Gerrit-Reviewer: Sean Liao <se...@liao.dev>
Gerrit-CC: Gopher Robot <go...@golang.org>
Gerrit-Attention: Zhandos Zhandarbek <zhandos.zh...@gmail.com>
Gerrit-Comment-Date: Fri, 25 Sep 2026 20:48:57 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: Yes
satisfied_requirement
unsatisfied_requirement
open
diffy

Michael Pratt (Gerrit)

unread,
Sep 28, 2026, 5:59:41 PM (6 days ago) Sep 28
to Zhandos Zhandarbek, goph...@pubsubhelper.golang.org, Michael Pratt, golang...@luci-project-accounts.iam.gserviceaccount.com, Alan Donovan, Gopher Robot, golang-co...@googlegroups.com
Attention needed from Zhandos Zhandarbek

Michael Pratt voted Code-Review+1

Code-Review+1
Open in Gerrit

Related details

Attention is currently required from:
  • Zhandos Zhandarbek
Submit Requirements:
    • requirement satisfiedCode-Review
    • requirement satisfiedNo-Unresolved-Comments
    • requirement satisfiedReview-Enforcement
    • requirement satisfiedTryBots-Pass
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: go
    Gerrit-Branch: master
    Gerrit-Change-Id: Ic0cbe2fb8b5770699083321e90113e07f35a3c68
    Gerrit-Change-Number: 837945
    Gerrit-PatchSet: 3
    Gerrit-Owner: Zhandos Zhandarbek <zhandos.zh...@gmail.com>
    Gerrit-Reviewer: Alan Donovan <adon...@google.com>
    Gerrit-CC: Gopher Robot <go...@golang.org>
    Gerrit-Attention: Zhandos Zhandarbek <zhandos.zh...@gmail.com>
    Gerrit-Comment-Date: Mon, 28 Sep 2026 21:59:35 +0000
    Gerrit-HasComments: No
    Gerrit-Has-Labels: Yes
    satisfied_requirement
    open
    diffy

    Gopher Robot (Gerrit)

    unread,
    Sep 28, 2026, 6:00:53 PM (6 days ago) Sep 28
    to Zhandos Zhandarbek, goph...@pubsubhelper.golang.org, golang-...@googlegroups.com, Michael Pratt, golang...@luci-project-accounts.iam.gserviceaccount.com, Alan Donovan, golang-co...@googlegroups.com

    Gopher Robot submitted the change

    Change information

    Commit message:
    text/template: call eq, not, printf and comparisons directly


    CL 835445 added a fast path in safeCall for func(...any) string.
    eq, ne, lt, le, gt, ge, not and printf still go through
    reflect.Value.Call. In bitnami/charts, printf, not, eq and ne make up
    about 7.6% of all template actions; the other builtins are rare.

    Switch on the function type in safeCall and call these signatures
    directly. Functions with other signatures pay one type comparison
    (userFunc below). The call is still inside the existing recover.

    Old and new binaries run alternately on an Apple M4 Pro:

    pkg: html/template
    │ old │ new │
    │ sec/op │ sec/op vs base │
    EscapedExecuteBuiltinsPage-12 35.53µ ± 2% 21.95µ ± 2% -38.24% (p=0.000 n=15)

    │ old │ new │
    │ allocs/op │ allocs/op vs base │
    EscapedExecuteBuiltinsPage-12 564.0 ± 0% 364.0 ± 0% -35.46% (p=0.000 n=15)

    pkg: text/template
    │ old │ new │
    │ sec/op │ sec/op vs base │
    ExecuteBuiltins/eq-12 548.6n ± 2% 275.9n ± 4% -49.71% (p=0.000 n=15)
    ExecuteBuiltins/eqMulti-12 672.5n ± 3% 373.0n ± 2% -44.54% (p=0.000 n=15)
    ExecuteBuiltins/lt-12 476.1n ± 3% 265.4n ± 3% -44.26% (p=0.000 n=15)
    ExecuteBuiltins/not-12 362.3n ± 1% 198.2n ± 2% -45.29% (p=0.000 n=15)
    ExecuteBuiltins/printf-12 651.2n ± 2% 418.8n ± 3% -35.69% (p=0.000 n=15)
    ExecuteBuiltins/userFunc-12 361.7n ± 3% 356.6n ± 3% ~ (p=0.721 n=15)
    geomean 496.5n 305.2n -38.53%

    │ old │ new │
    │ allocs/op │ allocs/op vs base │
    ExecuteBuiltins/eq-12 14.000 ± 0% 9.000 ± 0% -35.71% (p=0.000 n=15)
    ExecuteBuiltins/eqMulti-12 18.00 ± 0% 13.00 ± 0% -27.78% (p=0.000 n=15)
    ExecuteBuiltins/lt-12 10.000 ± 0% 7.000 ± 0% -30.00% (p=0.000 n=15)
    ExecuteBuiltins/not-12 7.000 ± 0% 5.000 ± 0% -28.57% (p=0.000 n=15)
    ExecuteBuiltins/printf-12 14.00 ± 0% 11.00 ± 0% -21.43% (p=0.000 n=15)
    ExecuteBuiltins/userFunc-12 6.000 ± 0% 6.000 ± 0% ~ (p=1.000 n=15)

    Updates #81610
    Change-Id: Ic0cbe2fb8b5770699083321e90113e07f35a3c68
    Reviewed-by: Sean Liao <se...@liao.dev>
    Reviewed-by: Alan Donovan <adon...@google.com>
    Reviewed-by: Michael Pratt <mpr...@google.com>
    Auto-Submit: Sean Liao <se...@liao.dev>
    Files:
    • M src/html/template/escape_test.go
    • M src/text/template/exec_test.go
    • M src/text/template/funcs.go
    Change size: M
    Delta: 3 files changed, 160 insertions(+), 0 deletions(-)
    Branch: refs/heads/master
    Submit Requirements:
    Open in Gerrit
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: merged
    Gerrit-Project: go
    Gerrit-Branch: master
    Gerrit-Change-Id: Ic0cbe2fb8b5770699083321e90113e07f35a3c68
    Gerrit-Change-Number: 837945
    Gerrit-PatchSet: 4
    Gerrit-Owner: Zhandos Zhandarbek <zhandos.zh...@gmail.com>
    Gerrit-Reviewer: Alan Donovan <adon...@google.com>
    Gerrit-Reviewer: Gopher Robot <go...@golang.org>
    open
    diffy
    satisfied_requirement
    Reply all
    Reply to author
    Forward
    0 new messages