[go] cmd/compile: treat function parameters as probably constant in string comparison

12 views
Skip to first unread message

Yongqi Jia (Gerrit)

unread,
Feb 6, 2026, 5:04:33 AMFeb 6
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Yongqi Jia has uploaded the change for review

Commit message

cmd/compile: treat function parameters as probably constant in string comparison

When comparing strings in a loop like strs[i] == str vs str == strs[i],
the compiler needs to pick which string's length to use as the third argument
to memequal. If one is constant, memequal optimizations can inline the
comparison instead of calling runtime.memequal.

Previously, probablyConstant only recognized local variables (PAUTO) that
were initialized with constant values. This meant function parameters were
not considered probably constant, causing suboptimal code generation
when the parameter operand order differed.

This change extends probablyConstant to also recognize function parameters
(PPARAM) as constant within the function body. This allows the compiler to
generate optimal code regardless of operand order in string comparisons.

Benchmark test code:

func containsA(strs []string, str string) bool {
for i := range strs {
if strs[i] == str { return true }
}
return false
}

func containsB(strs []string, str string) bool {
for i := range strs {
if str == strs[i] { return true }
}
return false
}

Before this change, containsA generated code that used strs[i].len
as the memequal length argument, while containsB used str.len.
This caused approximately 4x performance difference in some benchmarks.

After this change, both functions use str.len (the parameter length)
as the memequal length argument. Benchmark results with various data sizes
(searching for the last element in arrays of 10, 100, 1000, 10000 strings):

name time/op
Small_A_Last-10 20.39ns
Small_B_Last-10 20.51ns
Medium_A_Last-10 235.0ns
Medium_B_Last-10 216.4ns
Large_A_Last-10 2089ns
Large_B_Last-10 2075ns
Huge_A_Last-10 20812ns
Huge_B_Last-10 20862ns
Large_A_NotFound-10 469.2ns
Large_B_NotFound-10 492.3ns

Both functions now generate the same size code (192 bytes) and show
consistent performance across all data sizes, with differences under 5 percent.

Fixes #74471
Change-Id: I257910bba0abb3dc738578d898e63ecfb261c3cc

Change diff

diff --git a/src/cmd/compile/internal/compare/compare.go b/src/cmd/compile/internal/compare/compare.go
index 11793fc..fb2db67 100644
--- a/src/cmd/compile/internal/compare/compare.go
+++ b/src/cmd/compile/internal/compare/compare.go
@@ -276,15 +276,20 @@
return false
}
name := n.(*ir.Name)
- if name.Class != ir.PAUTO {
- return false
- }
- if def := name.Defn; def == nil {
- // n starts out as the empty string
+ switch name.Class {
+ case ir.PPARAM:
+ // Function parameters are constant within the function body.
+ // Using the parameter's length allows better optimization when
+ // comparing against varying expressions like slice elements.
return true
- } else if def.Op() == ir.OAS && (def.(*ir.AssignStmt).Y == nil || def.(*ir.AssignStmt).Y.Op() == ir.OLITERAL) {
- // n starts out as a constant string
- return true
+ case ir.PAUTO:
+ if def := name.Defn; def == nil {
+ // n starts out as the empty string
+ return true
+ } else if def.Op() == ir.OAS && (def.(*ir.AssignStmt).Y == nil || def.(*ir.AssignStmt).Y.Op() == ir.OLITERAL) {
+ // n starts out as a constant string
+ return true
+ }
}
return false
}

Change information

Files:
  • M src/cmd/compile/internal/compare/compare.go
Change size: S
Delta: 1 file changed, 13 insertions(+), 8 deletions(-)
Open in Gerrit

Related details

Attention set is empty
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: I257910bba0abb3dc738578d898e63ecfb261c3cc
Gerrit-Change-Number: 742141
Gerrit-PatchSet: 1
Gerrit-Owner: Yongqi Jia <yongqi...@gmail.com>
unsatisfied_requirement
satisfied_requirement
open
diffy

Daniel Morsing (Gerrit)

unread,
Feb 6, 2026, 6:52:40 AMFeb 6
to Yongqi Jia, goph...@pubsubhelper.golang.org, Robert Griesemer, Keith Randall, Matthew Dempsky, Gopher Robot, golang-co...@googlegroups.com
Attention needed from Keith Randall, Matthew Dempsky, Robert Griesemer and Yongqi Jia

Daniel Morsing added 1 comment

File src/cmd/compile/internal/compare/compare.go
Line 281, Patchset 1 (Latest): // Function parameters are constant within the function body.
Daniel Morsing . unresolved

I don't think this is right. Parameters can be assigned to.

I think a better avenue for this optimization is to pull these string conversions out of the walk pass, emit a string equality instruction when we generate SSA and then lower it to actual calls/instructions depending on what we results we get in the prove pass. This is a much larger job however.

Open in Gerrit

Related details

Attention is currently required from:
  • Keith Randall
  • Matthew Dempsky
  • Robert Griesemer
  • Yongqi Jia
Submit Requirements:
    • requirement is not satisfiedCode-Review
    • requirement is not 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: I257910bba0abb3dc738578d898e63ecfb261c3cc
    Gerrit-Change-Number: 742141
    Gerrit-PatchSet: 1
    Gerrit-Owner: Yongqi Jia <yongqi...@gmail.com>
    Gerrit-Reviewer: Keith Randall <k...@golang.org>
    Gerrit-Reviewer: Matthew Dempsky <mat...@go.dev>
    Gerrit-Reviewer: Robert Griesemer <g...@golang.org>
    Gerrit-CC: Daniel Morsing <daniel....@gmail.com>
    Gerrit-CC: Gopher Robot <go...@golang.org>
    Gerrit-Attention: Keith Randall <k...@golang.org>
    Gerrit-Attention: Matthew Dempsky <mat...@go.dev>
    Gerrit-Attention: Yongqi Jia <yongqi...@gmail.com>
    Gerrit-Attention: Robert Griesemer <g...@golang.org>
    Gerrit-Comment-Date: Fri, 06 Feb 2026 11:52:32 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    unsatisfied_requirement
    open
    diffy

    Keith Randall (Gerrit)

    unread,
    Feb 6, 2026, 11:07:20 AMFeb 6
    to Yongqi Jia, goph...@pubsubhelper.golang.org, Daniel Morsing, Robert Griesemer, Keith Randall, Matthew Dempsky, Gopher Robot, golang-co...@googlegroups.com
    Attention needed from Matthew Dempsky, Robert Griesemer and Yongqi Jia

    Keith Randall added 1 comment

    Patchset-level comments
    File-level comment, Patchset 1 (Latest):
    Keith Randall . unresolved

    This doesn't make any sense to me. The length of an argument string is *never* constant. (Unless it is possibly reassigned.)

    Open in Gerrit

    Related details

    Attention is currently required from:
    • Matthew Dempsky
    • Robert Griesemer
    • Yongqi Jia
    Submit Requirements:
    • requirement is not satisfiedCode-Review
    • requirement is not 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: I257910bba0abb3dc738578d898e63ecfb261c3cc
    Gerrit-Change-Number: 742141
    Gerrit-PatchSet: 1
    Gerrit-Owner: Yongqi Jia <yongqi...@gmail.com>
    Gerrit-Reviewer: Keith Randall <k...@golang.org>
    Gerrit-Reviewer: Matthew Dempsky <mat...@go.dev>
    Gerrit-Reviewer: Robert Griesemer <g...@golang.org>
    Gerrit-CC: Daniel Morsing <daniel....@gmail.com>
    Gerrit-CC: Gopher Robot <go...@golang.org>
    Gerrit-Attention: Matthew Dempsky <mat...@go.dev>
    Gerrit-Attention: Yongqi Jia <yongqi...@gmail.com>
    Gerrit-Attention: Robert Griesemer <g...@golang.org>
    Gerrit-Comment-Date: Fri, 06 Feb 2026 16:07:14 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    unsatisfied_requirement
    open
    diffy

    Keith Randall (Gerrit)

    unread,
    Feb 6, 2026, 6:44:01 PMFeb 6
    to Yongqi Jia, goph...@pubsubhelper.golang.org, Jorropo, Daniel Morsing, Robert Griesemer, Keith Randall, Matthew Dempsky, Gopher Robot, golang-co...@googlegroups.com
    Attention needed from Matthew Dempsky, Robert Griesemer and Yongqi Jia

    Keith Randall added 1 comment

    File src/cmd/compile/internal/compare/compare.go
    Line 297, Patchset 1 (Latest): if probablyConstant(t) && !probablyConstant(s) {
    Keith Randall . unresolved

    Note that I think this (original) code is largely irrelevant after CL 403735. (That CL will make the argument to memeq a constant if either string length is a constant, and the memeq call is inside the `if len(s)==len(t)` test.)

    So maybe we could repurpose this code to be "prefer to use a value from an outer loop" or something like that.

    That said, I'm not sure I buy the benchmarks. The CL description claims 4x improvement but then the benchmarks show almost no improvement and some are negative. I can't run them myself, as they are just in the comments, not as real benchmarks in the CL.

    Gerrit-CC: Jorropo <jorro...@gmail.com>
    Gerrit-Attention: Matthew Dempsky <mat...@go.dev>
    Gerrit-Attention: Yongqi Jia <yongqi...@gmail.com>
    Gerrit-Attention: Robert Griesemer <g...@golang.org>
    Gerrit-Comment-Date: Fri, 06 Feb 2026 23:43:58 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    unsatisfied_requirement
    open
    diffy

    Yongqi Jia (Gerrit)

    unread,
    Feb 7, 2026, 11:37:08 AMFeb 7
    to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
    Attention needed from Matthew Dempsky, Robert Griesemer and Yongqi Jia

    Yongqi Jia uploaded new patchset

    Yongqi Jia uploaded patch set #2 to this change.
    Open in Gerrit

    Related details

    Attention is currently required from:
    • Matthew Dempsky
    • Robert Griesemer
    • Yongqi Jia
    Submit Requirements:
    • requirement is not satisfiedCode-Review
    • requirement is not 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: I257910bba0abb3dc738578d898e63ecfb261c3cc
    Gerrit-Change-Number: 742141
    Gerrit-PatchSet: 2
    unsatisfied_requirement
    open
    diffy

    Yongqi Jia (Gerrit)

    unread,
    Feb 8, 2026, 9:00:11 PMFeb 8
    to goph...@pubsubhelper.golang.org, Jorropo, Daniel Morsing, Robert Griesemer, Keith Randall, Matthew Dempsky, Gopher Robot, golang-co...@googlegroups.com
    Attention needed from Daniel Morsing, Keith Randall, Matthew Dempsky and Robert Griesemer

    Yongqi Jia added 3 comments

    Patchset-level comments
    File-level comment, Patchset 1:
    Keith Randall . resolved

    This doesn't make any sense to me. The length of an argument string is *never* constant. (Unless it is possibly reassigned.)

    Yongqi Jia

    You're absolutely right. I made an incorrect assumption that function parameters are constant within the function body.

    After reviewing ir/reassignment.go, I see that:

    • Parameters (PPARAM) can be reassigned: `str = "new value"`
    • The ReassignOracle specifically handles this case (line 85-86: "any assignment to a param invalidates the entry")

    My change blindly treats all PPARAM as "probably constant" without checking if they've been reassigned, which is wrong.

    I'll either:
    1. Withdraw this CL and investigate the SSA-level approach Daniel suggested
    2. Or revise the change to use ReassignOracle to properly check for reassignment

    Thank you for catching this fundamental flaw.


    You're right — "probably constant" is a misnomer for parameters. I've reframed the CL as "prefer loop-invariant values": a non-reassigned PPARAM's length doesn't change across loop iterations, so passing it to memequal is preferable for branch prediction and code layout. Updated the title and comments accordingly.

    File src/cmd/compile/internal/compare/compare.go
    Line 281, Patchset 1: // Function parameters are constant within the function body.
    Daniel Morsing . resolved

    I don't think this is right. Parameters can be assigned to.

    I think a better avenue for this optimization is to pull these string conversions out of the walk pass, emit a string equality instruction when we generate SSA and then lower it to actual calls/instructions depending on what we results we get in the prove pass. This is a much larger job however.

    Yongqi Jia

    Thank you for the review, Daniel. You're right that parameters can be assigned to — my change incorrectly treats all PPARAM as "probably constant" without checking for reassignment.

    Your suggestion about moving the string comparison optimization to the SSA level is interesting. If I understand correctly, the idea would be:

    1. In walk pass: emit a generic "string equality" SSA instruction instead of immediately lowering to len check + memequal
    2. In prove pass: determine which string's length is constant (or invariant)
    3. In lower pass: emit the optimal memequal call based on prove results

    This would be more robust since the prove pass already has full dataflow analysis and can determine invariants more accurately than the heuristic-based probablyConstant function.

    I agree this is a much larger job. I'll look into the feasibility of this approach. In the meantime, would a smaller fix using ReassignOracle to properly check if the parameter has been reassigned be acceptable as an interim solution? Or would you prefer to wait for the SSA-level approach?

    Good point. The current code guards against that with ir.Reassigned(name) — it only returns true for parameters that are never reassigned in the function body. Updated the comment to make this clearer.

    Line 297, Patchset 1: if probablyConstant(t) && !probablyConstant(s) {
    Keith Randall . resolved

    Note that I think this (original) code is largely irrelevant after CL 403735. (That CL will make the argument to memeq a constant if either string length is a constant, and the memeq call is inside the `if len(s)==len(t)` test.)

    So maybe we could repurpose this code to be "prefer to use a value from an outer loop" or something like that.

    That said, I'm not sure I buy the benchmarks. The CL description claims 4x improvement but then the benchmarks show almost no improvement and some are negative. I can't run them myself, as they are just in the comments, not as real benchmarks in the CL.

    Yongqi Jia

    Thank you for the detailed review, Keith.

    You're right about CL 403735 (and actually CL 485535 which you authored). I should have investigated the SSA-level optimizations more thoroughly before proposing this change.

    Regarding the benchmarks: I apologize for the confusion. The "4x improvement" I mentioned was from the original issue #74471 reporter's benchmark, comparing Go 1.24.1 behavior. After my change, my benchmarks show both versions are nearly identical (~11.2 ns/op), which suggests the fix works - but you're correct that:

    1. The improvement is relative to the "before" state, not between the two benchmark functions
    2. The benchmarks should be included as actual test files in the CL, not just in commit messages

    I can add proper benchmark tests to the CL if that would help verify the change.

    However, I now understand that Daniel and you have raised a fundamental issue: PPARAM can be reassigned within a function body, so treating it as "probably constant" is incorrect. I'll need to rethink this approach.

    Would using the ReassignOracle to check if the parameter has been reassigned be a valid approach? Or do you think the SSA-level optimizations in CL 485535 already handle this case sufficiently?


    Thanks for pointing out CL 403735. That CL handles the case where prove can fold an actual constant length after the len==len check. The case here is different: the parameter length isn't a compile-time constant, but it is loop-invariant. The benefit is that memequal sees the same length value on every iteration rather than a varying slice-element length.

    Regarding benchmarks: I've dropped the "4x" claim. The CL now includes proper benchmark tests in bench_test.go. On arm64 (M1 Pro), benchstat shows ~5-7% improvement with p<0.005 (n=8). The assembly diff on amd64 confirms the optimization fires (MOVQ R8, CX → MOVQ SI, CX), though the runtime difference there is smaller.

    Open in Gerrit

    Related details

    Attention is currently required from:
    • Daniel Morsing
    • Keith Randall
    • Matthew Dempsky
    • Robert Griesemer
      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: I257910bba0abb3dc738578d898e63ecfb261c3cc
        Gerrit-Change-Number: 742141
        Gerrit-PatchSet: 2
        Gerrit-Owner: Yongqi Jia <yongqi...@gmail.com>
        Gerrit-Reviewer: Keith Randall <k...@golang.org>
        Gerrit-Reviewer: Matthew Dempsky <mat...@go.dev>
        Gerrit-Reviewer: Robert Griesemer <g...@golang.org>
        Gerrit-CC: Daniel Morsing <daniel....@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        Gerrit-CC: Jorropo <jorro...@gmail.com>
        Gerrit-Attention: Keith Randall <k...@golang.org>
        Gerrit-Attention: Matthew Dempsky <mat...@go.dev>
        Gerrit-Attention: Robert Griesemer <g...@golang.org>
        Gerrit-Attention: Daniel Morsing <daniel....@gmail.com>
        Gerrit-Comment-Date: Mon, 09 Feb 2026 02:00:05 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        Comment-In-Reply-To: Keith Randall <k...@golang.org>
        Comment-In-Reply-To: Daniel Morsing <daniel....@gmail.com>
        unsatisfied_requirement
        satisfied_requirement
        open
        diffy

        Yongqi Jia (Gerrit)

        unread,
        Feb 8, 2026, 9:02:21 PMFeb 8
        to goph...@pubsubhelper.golang.org, Jorropo, Daniel Morsing, Robert Griesemer, Keith Randall, Matthew Dempsky, Gopher Robot, golang-co...@googlegroups.com
        Attention needed from Daniel Morsing, Keith Randall, Matthew Dempsky and Robert Griesemer

        Yongqi Jia voted Code-Review+1

        Code-Review+1
        Open in Gerrit

        Related details

        Attention is currently required from:
        • Daniel Morsing
        • Keith Randall
        • Matthew Dempsky
        • Robert Griesemer
        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: I257910bba0abb3dc738578d898e63ecfb261c3cc
        Gerrit-Change-Number: 742141
        Gerrit-PatchSet: 2
        Gerrit-Owner: Yongqi Jia <yongqi...@gmail.com>
        Gerrit-Reviewer: Keith Randall <k...@golang.org>
        Gerrit-Reviewer: Matthew Dempsky <mat...@go.dev>
        Gerrit-Reviewer: Robert Griesemer <g...@golang.org>
        Gerrit-Reviewer: Yongqi Jia <yongqi...@gmail.com>
        Gerrit-CC: Daniel Morsing <daniel....@gmail.com>
        Gerrit-CC: Gopher Robot <go...@golang.org>
        Gerrit-CC: Jorropo <jorro...@gmail.com>
        Gerrit-Attention: Keith Randall <k...@golang.org>
        Gerrit-Attention: Matthew Dempsky <mat...@go.dev>
        Gerrit-Attention: Robert Griesemer <g...@golang.org>
        Gerrit-Attention: Daniel Morsing <daniel....@gmail.com>
        Gerrit-Comment-Date: Mon, 09 Feb 2026 02:02:15 +0000
        Gerrit-HasComments: No
        Gerrit-Has-Labels: Yes
        unsatisfied_requirement
        satisfied_requirement
        open
        diffy

        Yongqi Jia (Gerrit)

        unread,
        Feb 8, 2026, 9:16:07 PMFeb 8
        to goph...@pubsubhelper.golang.org, Jorropo, Daniel Morsing, Robert Griesemer, Keith Randall, Matthew Dempsky, Gopher Robot, golang-co...@googlegroups.com
        Attention needed from Daniel Morsing, Keith Randall, Matthew Dempsky and Robert Griesemer

        Yongqi Jia voted Code-Review+0

        Code-Review+0
        Gerrit-Comment-Date: Mon, 09 Feb 2026 02:16:03 +0000
        Gerrit-HasComments: No
        Gerrit-Has-Labels: Yes
        unsatisfied_requirement
        satisfied_requirement
        open
        diffy

        Keith Randall (Gerrit)

        unread,
        Feb 10, 2026, 12:42:11 PMFeb 10
        to Yongqi Jia, goph...@pubsubhelper.golang.org, Jorropo, Daniel Morsing, Robert Griesemer, Keith Randall, Matthew Dempsky, Gopher Robot, golang-co...@googlegroups.com
        Attention needed from Daniel Morsing, Matthew Dempsky, Robert Griesemer and Yongqi Jia

        Keith Randall added 1 comment

        File src/cmd/compile/internal/compare/compare.go
        Line 297, Patchset 1: if probablyConstant(t) && !probablyConstant(s) {
        Keith Randall . unresolved

        Note that I think this (original) code is largely irrelevant after CL 403735. (That CL will make the argument to memeq a constant if either string length is a constant, and the memeq call is inside the `if len(s)==len(t)` test.)

        So maybe we could repurpose this code to be "prefer to use a value from an outer loop" or something like that.

        That said, I'm not sure I buy the benchmarks. The CL description claims 4x improvement but then the benchmarks show almost no improvement and some are negative. I can't run them myself, as they are just in the comments, not as real benchmarks in the CL.

        Yongqi Jia

        Thank you for the detailed review, Keith.

        You're right about CL 403735 (and actually CL 485535 which you authored). I should have investigated the SSA-level optimizations more thoroughly before proposing this change.

        Regarding the benchmarks: I apologize for the confusion. The "4x improvement" I mentioned was from the original issue #74471 reporter's benchmark, comparing Go 1.24.1 behavior. After my change, my benchmarks show both versions are nearly identical (~11.2 ns/op), which suggests the fix works - but you're correct that:

        1. The improvement is relative to the "before" state, not between the two benchmark functions
        2. The benchmarks should be included as actual test files in the CL, not just in commit messages

        I can add proper benchmark tests to the CL if that would help verify the change.

        However, I now understand that Daniel and you have raised a fundamental issue: PPARAM can be reassigned within a function body, so treating it as "probably constant" is incorrect. I'll need to rethink this approach.

        Would using the ReassignOracle to check if the parameter has been reassigned be a valid approach? Or do you think the SSA-level optimizations in CL 485535 already handle this case sufficiently?


        Thanks for pointing out CL 403735. That CL handles the case where prove can fold an actual constant length after the len==len check. The case here is different: the parameter length isn't a compile-time constant, but it is loop-invariant. The benefit is that memequal sees the same length value on every iteration rather than a varying slice-element length.

        Regarding benchmarks: I've dropped the "4x" claim. The CL now includes proper benchmark tests in bench_test.go. On arm64 (M1 Pro), benchstat shows ~5-7% improvement with p<0.005 (n=8). The assembly diff on amd64 confirms the optimization fires (MOVQ R8, CX → MOVQ SI, CX), though the runtime difference there is smaller.

        Keith Randall

        I can add proper benchmark tests to the CL if that would help verify the change.

        Yes, thanks.

        However, I now understand that Daniel and you have raised a fundamental issue: PPARAM can be reassigned within a function body, so treating it as "probably constant" is incorrect. I'll need to rethink this approach.

        I don't think this is a serious problem. We're only talking heuristics here, not correctness, so assuming parameters aren't reassigned is a fine initial design.

        The benefit is that memequal sees the same length value on every iteration rather than a varying slice-element length.

        That doesn't sound right to me. `memequal` only gets called after the length check, so `memequal`'s length argument will always be invariant if even one of the two strings being compared is invariant.

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Daniel Morsing
        • Matthew Dempsky
        • Robert Griesemer
        • Yongqi Jia
          Submit Requirements:
            • requirement is not satisfiedCode-Review
            • requirement is not 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: I257910bba0abb3dc738578d898e63ecfb261c3cc
            Gerrit-Change-Number: 742141
            Gerrit-PatchSet: 2
            Gerrit-Owner: Yongqi Jia <yongqi...@gmail.com>
            Gerrit-Reviewer: Keith Randall <k...@golang.org>
            Gerrit-Reviewer: Matthew Dempsky <mat...@go.dev>
            Gerrit-Reviewer: Robert Griesemer <g...@golang.org>
            Gerrit-Reviewer: Yongqi Jia <yongqi...@gmail.com>
            Gerrit-CC: Daniel Morsing <daniel....@gmail.com>
            Gerrit-CC: Gopher Robot <go...@golang.org>
            Gerrit-CC: Jorropo <jorro...@gmail.com>
            Gerrit-Attention: Matthew Dempsky <mat...@go.dev>
            Gerrit-Attention: Yongqi Jia <yongqi...@gmail.com>
            Gerrit-Attention: Robert Griesemer <g...@golang.org>
            Gerrit-Attention: Daniel Morsing <daniel....@gmail.com>
            Gerrit-Comment-Date: Tue, 10 Feb 2026 17:42:06 +0000
            Gerrit-HasComments: Yes
            Gerrit-Has-Labels: No
            Comment-In-Reply-To: Keith Randall <k...@golang.org>
            Comment-In-Reply-To: Yongqi Jia <yongqi...@gmail.com>
            unsatisfied_requirement
            open
            diffy

            Keith Randall (Gerrit)

            unread,
            Feb 10, 2026, 12:45:25 PMFeb 10
            to Yongqi Jia, goph...@pubsubhelper.golang.org, Jorropo, Daniel Morsing, Robert Griesemer, Keith Randall, Matthew Dempsky, Gopher Robot, golang-co...@googlegroups.com
            Attention needed from Daniel Morsing, Matthew Dempsky, Robert Griesemer and Yongqi Jia

            Keith Randall added 1 comment

            File src/cmd/compile/internal/compare/compare.go
            Line 284, Patchset 2 (Latest): // passed across iterations, aiding branch prediction.
            Keith Randall . unresolved

            This I don't think I believe. I don't see any branches happening differently.

            Probably it is a latency of loads slowing retirement of instructions somehow? That I'm not sure about either. The length comparison outside of the `memequal` can't retire until the load completes, so the comparisons inside `memequal` should all be retireable at the same time in either case.

            Gerrit-Comment-Date: Tue, 10 Feb 2026 17:45:01 +0000
            Gerrit-HasComments: Yes
            Gerrit-Has-Labels: No
            unsatisfied_requirement
            open
            diffy

            Keith Randall (Gerrit)

            unread,
            Feb 10, 2026, 12:58:37 PMFeb 10
            to Yongqi Jia, goph...@pubsubhelper.golang.org, Jorropo, Daniel Morsing, Robert Griesemer, Keith Randall, Matthew Dempsky, Gopher Robot, golang-co...@googlegroups.com
            Attention needed from Daniel Morsing, Matthew Dempsky, Robert Griesemer and Yongqi Jia

            Keith Randall added 1 comment

            File src/cmd/compile/internal/test/bench_test.go
            Line 214, Patchset 2 (Latest):func BenchmarkStringCompare_ParamRight(b *testing.B) {
            Keith Randall . unresolved
            I tried these on one of my machines. This CL only makes the code slower :( containsParamRight, which passes the newly loaded size instead of the parameter's size to memequal, is the faster version at tip, by a little bit.
            ```
            goos: linux
            goarch: amd64
            pkg: cmd/compile/internal/test
            cpu: 12th Gen Intel(R) Core(TM) i7-12700
            │ tip │ exp │
            │ sec/op │ sec/op vs base │
            StringCompare_ParamRight-20 167.2n ± 1% 171.9n ± 2% +2.87% (p=0.000 n=10)
            StringCompare_ParamLeft-20 176.5n ± 2% 175.2n ± 6% ~ (p=0.051 n=10)
            geomean 171.7n 173.5n +1.05%
            ```
            (exp = tip + this CL)
            Gerrit-Comment-Date: Tue, 10 Feb 2026 17:58:31 +0000
            Gerrit-HasComments: Yes
            Gerrit-Has-Labels: No
            unsatisfied_requirement
            open
            diffy

            Yongqi Jia (Gerrit)

            unread,
            Feb 11, 2026, 10:58:22 PMFeb 11
            to goph...@pubsubhelper.golang.org, Jorropo, Daniel Morsing, Robert Griesemer, Keith Randall, Matthew Dempsky, Gopher Robot, golang-co...@googlegroups.com
            Attention needed from Daniel Morsing, Keith Randall, Matthew Dempsky and Robert Griesemer

            Yongqi Jia added 3 comments

            File src/cmd/compile/internal/compare/compare.go
            Line 284, Patchset 2 (Latest): // passed across iterations, aiding branch prediction.
            Keith Randall . resolved

            This I don't think I believe. I don't see any branches happening differently.

            Probably it is a latency of loads slowing retirement of instructions somehow? That I'm not sure about either. The length comparison outside of the `memequal` can't retire until the load completes, so the comparisons inside `memequal` should all be retireable at the same time in either case.

            Yongqi Jia

            You're right — the "aiding branch prediction" rationale in the comment was speculative and likely incorrect. Thank you for the analysis on memequal retirement.

            I've since done extensive benchmarking on Go tip (9777ceceec) with 30 samples on both amd64 (Intel Xeon Gold 6148, linux) and arm64 (Apple M1 Pro, darwin). The results confirm your point: ParamRight and ParamLeft show no statistically significant difference on the master baseline (amd64: 340.7ns vs 340.2ns, p=0.836), meaning this change has no measurable effect.

            I've updated Patch Set 3 to drop the compare.go change entirely and keep only the benchmark test (BenchmarkStringEqParamOrder) as a regression guard.

            Line 297, Patchset 1: if probablyConstant(t) && !probablyConstant(s) {
            Keith Randall . resolved

            Note that I think this (original) code is largely irrelevant after CL 403735. (That CL will make the argument to memeq a constant if either string length is a constant, and the memeq call is inside the `if len(s)==len(t)` test.)

            So maybe we could repurpose this code to be "prefer to use a value from an outer loop" or something like that.

            That said, I'm not sure I buy the benchmarks. The CL description claims 4x improvement but then the benchmarks show almost no improvement and some are negative. I can't run them myself, as they are just in the comments, not as real benchmarks in the CL.

            Yongqi Jia

            Thank you for the detailed review, Keith.

            You're right about CL 403735 (and actually CL 485535 which you authored). I should have investigated the SSA-level optimizations more thoroughly before proposing this change.

            Regarding the benchmarks: I apologize for the confusion. The "4x improvement" I mentioned was from the original issue #74471 reporter's benchmark, comparing Go 1.24.1 behavior. After my change, my benchmarks show both versions are nearly identical (~11.2 ns/op), which suggests the fix works - but you're correct that:

            1. The improvement is relative to the "before" state, not between the two benchmark functions
            2. The benchmarks should be included as actual test files in the CL, not just in commit messages

            I can add proper benchmark tests to the CL if that would help verify the change.

            However, I now understand that Daniel and you have raised a fundamental issue: PPARAM can be reassigned within a function body, so treating it as "probably constant" is incorrect. I'll need to rethink this approach.

            Would using the ReassignOracle to check if the parameter has been reassigned be a valid approach? Or do you think the SSA-level optimizations in CL 485535 already handle this case sufficiently?


            Thanks for pointing out CL 403735. That CL handles the case where prove can fold an actual constant length after the len==len check. The case here is different: the parameter length isn't a compile-time constant, but it is loop-invariant. The benefit is that memequal sees the same length value on every iteration rather than a varying slice-element length.

            Regarding benchmarks: I've dropped the "4x" claim. The CL now includes proper benchmark tests in bench_test.go. On arm64 (M1 Pro), benchstat shows ~5-7% improvement with p<0.005 (n=8). The assembly diff on amd64 confirms the optimization fires (MOVQ R8, CX → MOVQ SI, CX), though the runtime difference there is smaller.

            Keith Randall

            I can add proper benchmark tests to the CL if that would help verify the change.

            Yes, thanks.

            However, I now understand that Daniel and you have raised a fundamental issue: PPARAM can be reassigned within a function body, so treating it as "probably constant" is incorrect. I'll need to rethink this approach.

            I don't think this is a serious problem. We're only talking heuristics here, not correctness, so assuming parameters aren't reassigned is a fine initial design.

            The benefit is that memequal sees the same length value on every iteration rather than a varying slice-element length.

            That doesn't sound right to me. `memequal` only gets called after the length check, so `memequal`'s length argument will always be invariant if even one of the two strings being compared is invariant.

            Yongqi Jia

            You're right. Since memequal is only called after the length check passes (slen == tlen), the length argument to memequal is always the same value regardless of which side we pick — it just comes from a different register/source. So the "benefit" I described does not actually exist.

            The benchmarks confirm this: with 30 samples on amd64 (Intel Xeon Gold 6148), ParamRight and ParamLeft show no statistically significant difference on the master baseline (340.7ns vs 340.2ns, p=0.836).

            I've updated PS3 to drop the compare.go change and keep only the benchmark test as a regression guard.

            File src/cmd/compile/internal/test/bench_test.go
            Line 214, Patchset 2 (Latest):func BenchmarkStringCompare_ParamRight(b *testing.B) {
            Keith Randall . resolved
            I tried these on one of my machines. This CL only makes the code slower :( containsParamRight, which passes the newly loaded size instead of the parameter's size to memequal, is the faster version at tip, by a little bit.
            ```
            goos: linux
            goarch: amd64
            pkg: cmd/compile/internal/test
            cpu: 12th Gen Intel(R) Core(TM) i7-12700
            │ tip │ exp │
            │ sec/op │ sec/op vs base │
            StringCompare_ParamRight-20 167.2n ± 1% 171.9n ± 2% +2.87% (p=0.000 n=10)
            StringCompare_ParamLeft-20 176.5n ± 2% 175.2n ± 6% ~ (p=0.051 n=10)
            geomean 171.7n 173.5n +1.05%
            ```
            (exp = tip + this CL)
            Yongqi Jia

            Thanks for running the benchmarks on your machine, Keith. That's very helpful data.

            My benchmarks were run on arm64 (Apple M1 Pro, darwin), where I consistently see a ~5-7% improvement. Your results on amd64 show the opposite — a ~2.87% regression. This suggests the optimization has platform-dependent behavior.

            I'll try to analyze what causes this divergence. My current hypothesis is that arm64 and amd64 handle register pressure and load latency differently in this context — on arm64, reusing a loop-invariant register may reduce load-use stalls, while on amd64 the extra register pressure from keeping the invariant value alive across iterations might actually hurt.

            I'll investigate further and report back with more detailed analysis. If it turns out this optimization doesn't generalize across architectures, I'll abandon the CL.

            ----
            After extensive benchmarking on Go tip (9777ceceec) with 30 samples on both amd64 (Intel Xeon Gold 6148, linux) and arm64 (Apple M1 Pro, darwin), I found that the master baseline already generates equivalent code for both operand orderings — ParamRight and ParamLeft show no statistically significant difference on amd64 (340.7ns vs 340.2ns, p=0.836 / p=0.131).

            It appears that the current tip already handles this case well enough that the probablyConstant change for PPARAM has no measurable effect, though I have not traced exactly which pass is responsible.

            I've updated this CL (Patch Set 3) to drop the compare.go change entirely and keep only the benchmark test (BenchmarkStringEqParamOrder) as a regression guard, so that if a future compiler change breaks the symmetry, it will be caught.

            Open in Gerrit

            Related details

            Attention is currently required from:
            • Daniel Morsing
            • Keith Randall
            • Matthew Dempsky
            • Robert Griesemer
              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: I257910bba0abb3dc738578d898e63ecfb261c3cc
                Gerrit-Change-Number: 742141
                Gerrit-PatchSet: 2
                Gerrit-Owner: Yongqi Jia <yongqi...@gmail.com>
                Gerrit-Reviewer: Keith Randall <k...@golang.org>
                Gerrit-Reviewer: Matthew Dempsky <mat...@go.dev>
                Gerrit-Reviewer: Robert Griesemer <g...@golang.org>
                Gerrit-Reviewer: Yongqi Jia <yongqi...@gmail.com>
                Gerrit-CC: Daniel Morsing <daniel....@gmail.com>
                Gerrit-CC: Gopher Robot <go...@golang.org>
                Gerrit-CC: Jorropo <jorro...@gmail.com>
                Gerrit-Attention: Keith Randall <k...@golang.org>
                Gerrit-Attention: Matthew Dempsky <mat...@go.dev>
                Gerrit-Attention: Robert Griesemer <g...@golang.org>
                Gerrit-Attention: Daniel Morsing <daniel....@gmail.com>
                Gerrit-Comment-Date: Thu, 12 Feb 2026 03:58:16 +0000
                Gerrit-HasComments: Yes
                Gerrit-Has-Labels: No
                unsatisfied_requirement
                satisfied_requirement
                open
                diffy

                Yongqi Jia (Gerrit)

                unread,
                Feb 12, 2026, 1:14:23 AMFeb 12
                to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
                Attention needed from Daniel Morsing, Keith Randall, Matthew Dempsky and Robert Griesemer

                Yongqi Jia uploaded new patchset

                Yongqi Jia uploaded patch set #3 to this change.
                Open in Gerrit

                Related details

                Attention is currently required from:
                • Daniel Morsing
                • Keith Randall
                • Matthew Dempsky
                • Robert Griesemer
                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: I257910bba0abb3dc738578d898e63ecfb261c3cc
                Gerrit-Change-Number: 742141
                Gerrit-PatchSet: 3
                unsatisfied_requirement
                satisfied_requirement
                open
                diffy

                Yongqi Jia (Gerrit)

                unread,
                Feb 24, 2026, 2:46:02 AM (4 days ago) Feb 24
                to goph...@pubsubhelper.golang.org, Jorropo, Daniel Morsing, Robert Griesemer, Keith Randall, Matthew Dempsky, Gopher Robot, golang-co...@googlegroups.com
                Attention needed from Daniel Morsing, Keith Randall, Matthew Dempsky and Robert Griesemer

                Yongqi Jia added 1 comment

                Patchset-level comments
                File-level comment, Patchset 3 (Latest):
                Yongqi Jia . resolved

                Hi Keith,

                Just checking in on this CL. Patch Set 3 drops all the compiler changes and only keeps the benchmark (BenchmarkStringEqParamOrder) as a regression guard, based on our discussion that the current tip already generates equivalent code for both operand orders.

                Is this benchmark-only approach acceptable, or would you prefer I abandon this CL? Happy to go either way.

                Open in Gerrit

                Related details

                Attention is currently required from:
                • Daniel Morsing
                • Keith Randall
                • Matthew Dempsky
                • Robert Griesemer
                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: I257910bba0abb3dc738578d898e63ecfb261c3cc
                Gerrit-Change-Number: 742141
                Gerrit-PatchSet: 3
                Gerrit-Owner: Yongqi Jia <yongqi...@gmail.com>
                Gerrit-Reviewer: Keith Randall <k...@golang.org>
                Gerrit-Reviewer: Matthew Dempsky <mat...@go.dev>
                Gerrit-Reviewer: Robert Griesemer <g...@golang.org>
                Gerrit-Reviewer: Yongqi Jia <yongqi...@gmail.com>
                Gerrit-CC: Daniel Morsing <daniel....@gmail.com>
                Gerrit-CC: Gopher Robot <go...@golang.org>
                Gerrit-CC: Jorropo <jorro...@gmail.com>
                Gerrit-Attention: Keith Randall <k...@golang.org>
                Gerrit-Attention: Matthew Dempsky <mat...@go.dev>
                Gerrit-Attention: Robert Griesemer <g...@golang.org>
                Gerrit-Attention: Daniel Morsing <daniel....@gmail.com>
                Gerrit-Comment-Date: Tue, 24 Feb 2026 07:45:58 +0000
                Gerrit-HasComments: Yes
                Gerrit-Has-Labels: No
                unsatisfied_requirement
                satisfied_requirement
                open
                diffy

                Keith Randall (Gerrit)

                unread,
                12:55 PM (2 hours ago) 12:55 PM
                to Yongqi Jia, goph...@pubsubhelper.golang.org, Keith Randall, Jorropo, Daniel Morsing, Robert Griesemer, Matthew Dempsky, Gopher Robot, golang-co...@googlegroups.com
                Attention needed from Daniel Morsing, Matthew Dempsky, Robert Griesemer and Yongqi Jia

                Keith Randall voted and added 1 comment

                Votes added by Keith Randall

                Auto-Submit+1
                Code-Review+2
                Commit-Queue+1

                1 comment

                Commit Message
                Line 11, Patchset 3 (Latest):affect performance when the comparand is a function parameter.
                Keith Randall . resolved

                I don't think I've ever seen this word before. I like it.

                Open in Gerrit

                Related details

                Attention is currently required from:
                • Daniel Morsing
                • Matthew Dempsky
                • Robert Griesemer
                • Yongqi Jia
                  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: I257910bba0abb3dc738578d898e63ecfb261c3cc
                  Gerrit-Change-Number: 742141
                  Gerrit-PatchSet: 3
                  Gerrit-Owner: Yongqi Jia <yongqi...@gmail.com>
                  Gerrit-Reviewer: Keith Randall <k...@golang.org>
                  Gerrit-Reviewer: Matthew Dempsky <mat...@go.dev>
                  Gerrit-Reviewer: Robert Griesemer <g...@golang.org>
                  Gerrit-Reviewer: Yongqi Jia <yongqi...@gmail.com>
                  Gerrit-CC: Daniel Morsing <daniel....@gmail.com>
                  Gerrit-CC: Gopher Robot <go...@golang.org>
                  Gerrit-CC: Jorropo <jorro...@gmail.com>
                  Gerrit-Attention: Matthew Dempsky <mat...@go.dev>
                  Gerrit-Attention: Yongqi Jia <yongqi...@gmail.com>
                  Gerrit-Attention: Robert Griesemer <g...@golang.org>
                  Gerrit-Attention: Daniel Morsing <daniel....@gmail.com>
                  Gerrit-Comment-Date: Fri, 27 Feb 2026 17:54:58 +0000
                  Gerrit-HasComments: Yes
                  Gerrit-Has-Labels: Yes
                  satisfied_requirement
                  unsatisfied_requirement
                  open
                  diffy

                  Keith Randall (Gerrit)

                  unread,
                  12:55 PM (2 hours ago) 12:55 PM
                  to Yongqi Jia, goph...@pubsubhelper.golang.org, Go LUCI, Keith Randall, Jorropo, Daniel Morsing, Robert Griesemer, Matthew Dempsky, Gopher Robot, golang-co...@googlegroups.com
                  Attention needed from Daniel Morsing, Matthew Dempsky, Robert Griesemer and Yongqi Jia

                  Keith Randall voted Code-Review+1

                  Code-Review+1
                  Open in Gerrit

                  Related details

                  Attention is currently required from:
                  • Daniel Morsing
                  • Matthew Dempsky
                  • Robert Griesemer
                  • Yongqi Jia
                  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: I257910bba0abb3dc738578d898e63ecfb261c3cc
                  Gerrit-Change-Number: 742141
                  Gerrit-PatchSet: 3
                  Gerrit-Owner: Yongqi Jia <yongqi...@gmail.com>
                  Gerrit-Reviewer: Keith Randall <k...@golang.org>
                  Gerrit-Reviewer: Keith Randall <k...@google.com>
                  Gerrit-Reviewer: Matthew Dempsky <mat...@go.dev>
                  Gerrit-Reviewer: Robert Griesemer <g...@golang.org>
                  Gerrit-Reviewer: Yongqi Jia <yongqi...@gmail.com>
                  Gerrit-CC: Daniel Morsing <daniel....@gmail.com>
                  Gerrit-CC: Gopher Robot <go...@golang.org>
                  Gerrit-CC: Jorropo <jorro...@gmail.com>
                  Gerrit-Attention: Matthew Dempsky <mat...@go.dev>
                  Gerrit-Attention: Yongqi Jia <yongqi...@gmail.com>
                  Gerrit-Attention: Robert Griesemer <g...@golang.org>
                  Gerrit-Attention: Daniel Morsing <daniel....@gmail.com>
                  Gerrit-Comment-Date: Fri, 27 Feb 2026 17:55:15 +0000
                  Gerrit-HasComments: No
                  Gerrit-Has-Labels: Yes
                  satisfied_requirement
                  unsatisfied_requirement
                  open
                  diffy
                  Reply all
                  Reply to author
                  Forward
                  0 new messages