[go] strings, bytes: speed up TrimSpace for Unicode input

3 views
Skip to first unread message

Chencheng Jiang (Gerrit)

unread,
Jun 23, 2026, 11:51:37 PMJun 23
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Chencheng Jiang has uploaded the change for review

Commit message

strings, bytes: speed up TrimSpace for Unicode input

Avoid the generic TrimFunc fallback in TrimSpace after encountering
non-ASCII input. Instead, use dedicated Unicode whitespace trimming
helpers backed by a small internal White_Space predicate.

This keeps the existing ASCII fast path unchanged while avoiding
function predicate calls and general Unicode range table lookups on
the Unicode trimming path.

On linux/amd64:

name old time/op new time/op delta
strings/TrimSpace/NoTrim 1.54ns 1.51ns -1.8%
strings/TrimSpace/ASCII 2.26ns 2.28ns +0.6%
strings/TrimSpace/SomeNonASCII 48.7ns 22.5ns -53.8%
strings/TrimSpace/JustNonASCII 61.2ns 31.0ns -49.3%
bytes/TrimSpace/NoTrim 1.53ns 1.51ns -1.5%
bytes/TrimSpace/ASCII 2.46ns 2.47ns +0.5%
bytes/TrimSpace/SomeNonASCII 46.8ns 24.4ns -47.8%
bytes/TrimSpace/JustNonASCII 63.3ns 33.2ns -47.5%

All cases remain at 0 B/op and 0 allocs/op.

Add a test to keep the internal predicate in sync with unicode.IsSpace.
Change-Id: I4fc0cff5808169b562e156eafa038450c2fe2d50

Change diff

diff --git a/src/bytes/bytes.go b/src/bytes/bytes.go
index b0daa5d..669d5b0 100644
--- a/src/bytes/bytes.go
+++ b/src/bytes/bytes.go
@@ -8,6 +8,7 @@

import (
"internal/bytealg"
+ "internal/stringslite"
"math/bits"
"unicode"
"unicode/utf8"
@@ -1106,6 +1107,38 @@
return s
}

+func trimSpaceUnicode(s []byte) []byte {
+ for len(s) > 0 {
+ r, n := rune(s[0]), 1
+ if r >= utf8.RuneSelf {
+ r, n = utf8.DecodeRune(s)
+ }
+ if !stringslite.IsSpace(r) {
+ break
+ }
+ s = s[n:]
+ }
+ if len(s) == 0 {
+ // This is what we've historically done.
+ return nil
+ }
+ return trimRightSpaceUnicode(s)
+}
+
+func trimRightSpaceUnicode(s []byte) []byte {
+ for len(s) > 0 {
+ r, n := rune(s[len(s)-1]), 1
+ if r >= utf8.RuneSelf {
+ r, n = utf8.DecodeLastRune(s)
+ }
+ if !stringslite.IsSpace(r) {
+ break
+ }
+ s = s[:len(s)-n]
+ }
+ return s
+}
+
// TrimSpace returns a subslice of s by slicing off all leading and
// trailing white space, as defined by Unicode.
func TrimSpace(s []byte) []byte {
@@ -1114,7 +1147,7 @@
if c >= utf8.RuneSelf {
// If we run into a non-ASCII byte, fall back to the
// slower unicode-aware method on the remaining bytes.
- return TrimFunc(s[lo:], unicode.IsSpace)
+ return trimSpaceUnicode(s[lo:])
}
if asciiSpace[c] != 0 {
continue
@@ -1124,7 +1157,7 @@
for hi := len(s) - 1; hi >= 0; hi-- {
c := s[hi]
if c >= utf8.RuneSelf {
- return TrimFunc(s[:hi+1], unicode.IsSpace)
+ return trimRightSpaceUnicode(s[:hi+1])
}
if asciiSpace[c] == 0 {
// At this point, s[:hi+1] starts and ends with ASCII
diff --git a/src/internal/stringslite/strings.go b/src/internal/stringslite/strings.go
index 6c825df..830a05b 100644
--- a/src/internal/stringslite/strings.go
+++ b/src/internal/stringslite/strings.go
@@ -5,7 +5,7 @@
// Package stringslite implements a subset of strings,
// only using packages that may be imported by "os".
//
-// Tests for these functions are in the strings package.
+// Most tests for these functions are in the strings package.
package stringslite

import (
@@ -112,6 +112,28 @@
return s
}

+// IsSpace reports whether r is a space character as defined by
+// Unicode's White Space property.
+func IsSpace(r rune) bool {
+ if r <= ' ' {
+ return r == ' ' || '\t' <= r && r <= '\r'
+ }
+ if r < 0x85 {
+ return false
+ }
+ if r == 0x85 || r == 0xA0 || r == 0x1680 {
+ return true
+ }
+ if 0x2000 <= r && r <= 0x200A {
+ return true
+ }
+ switch r {
+ case 0x2028, 0x2029, 0x202F, 0x205F, 0x3000:
+ return true
+ }
+ return false
+}
+
func Clone(s string) string {
if len(s) == 0 {
return ""
diff --git a/src/internal/stringslite/strings_test.go b/src/internal/stringslite/strings_test.go
new file mode 100644
index 0000000..2f3df52
--- /dev/null
+++ b/src/internal/stringslite/strings_test.go
@@ -0,0 +1,20 @@
+// Copyright 2026 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package stringslite_test
+
+import (
+ "internal/stringslite"
+ "testing"
+ "unicode"
+ "unicode/utf8"
+)
+
+func TestIsSpace(t *testing.T) {
+ for r := rune(0); r <= utf8.MaxRune; r++ {
+ if stringslite.IsSpace(r) != unicode.IsSpace(r) {
+ t.Fatalf("IsSpace(%U) = %v, want %v", r, stringslite.IsSpace(r), unicode.IsSpace(r))
+ }
+ }
+}
diff --git a/src/strings/strings.go b/src/strings/strings.go
index 3e5de2f..d366d52 100644
--- a/src/strings/strings.go
+++ b/src/strings/strings.go
@@ -1083,6 +1083,34 @@
return s
}

+func trimSpaceUnicode(s string) string {
+ for len(s) > 0 {
+ r, n := rune(s[0]), 1
+ if r >= utf8.RuneSelf {
+ r, n = utf8.DecodeRuneInString(s)
+ }
+ if !stringslite.IsSpace(r) {
+ break
+ }
+ s = s[n:]
+ }
+ return trimRightSpaceUnicode(s)
+}
+
+func trimRightSpaceUnicode(s string) string {
+ for len(s) > 0 {
+ r, n := rune(s[len(s)-1]), 1
+ if r >= utf8.RuneSelf {
+ r, n = utf8.DecodeLastRuneInString(s)
+ }
+ if !stringslite.IsSpace(r) {
+ break
+ }
+ s = s[:len(s)-n]
+ }
+ return s
+}
+
// TrimSpace returns a slice (substring) of the string s,
// with all leading and trailing white space removed,
// as defined by Unicode.
@@ -1092,7 +1120,7 @@
if c >= utf8.RuneSelf {
// If we run into a non-ASCII byte, fall back to the
// slower unicode-aware method on the remaining bytes.
- return TrimFunc(s[lo:], unicode.IsSpace)
+ return trimSpaceUnicode(s[lo:])
}
if asciiSpace[c] != 0 {
continue
@@ -1102,7 +1130,7 @@
for hi := len(s) - 1; hi >= 0; hi-- {
c := s[hi]
if c >= utf8.RuneSelf {
- return TrimRightFunc(s[:hi+1], unicode.IsSpace)
+ return trimRightSpaceUnicode(s[:hi+1])
}
if asciiSpace[c] == 0 {
// At this point, s[:hi+1] starts and ends with ASCII

Change information

Files:
  • M src/bytes/bytes.go
  • M src/internal/stringslite/strings.go
  • A src/internal/stringslite/strings_test.go
  • M src/strings/strings.go
Change size: M
Delta: 4 files changed, 108 insertions(+), 5 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: I4fc0cff5808169b562e156eafa038450c2fe2d50
Gerrit-Change-Number: 793620
Gerrit-PatchSet: 1
Gerrit-Owner: Chencheng Jiang <dorb...@gmail.com>
unsatisfied_requirement
satisfied_requirement
open
diffy

Sean Liao (Gerrit)

unread,
5:58 PM (5 hours ago) 5:58 PM
to Chencheng Jiang, goph...@pubsubhelper.golang.org, Brad Fitzpatrick, Ian Lance Taylor, Robert Griesemer, Gopher Robot, golang-co...@googlegroups.com
Attention needed from Brad Fitzpatrick, Chencheng Jiang, Ian Lance Taylor and Robert Griesemer

Sean Liao added 2 comments

Commit Message
Line 27, Patchset 1 (Latest):bytes/TrimSpace/JustNonASCII 63.3ns 33.2ns -47.5%
Sean Liao . unresolved
File src/internal/stringslite/strings.go
Line 118, Patchset 1 (Latest): if r <= ' ' {
Sean Liao . unresolved

can this just be one big switch block?
the ranges aren't that big either.
and comment on the hex codes what those runes are.

Open in Gerrit

Related details

Attention is currently required from:
  • Brad Fitzpatrick
  • Chencheng Jiang
  • Ian Lance Taylor
  • Robert Griesemer
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: I4fc0cff5808169b562e156eafa038450c2fe2d50
    Gerrit-Change-Number: 793620
    Gerrit-PatchSet: 1
    Gerrit-Owner: Chencheng Jiang <dorb...@gmail.com>
    Gerrit-Reviewer: Brad Fitzpatrick <brad...@golang.org>
    Gerrit-Reviewer: Ian Lance Taylor <ia...@golang.org>
    Gerrit-Reviewer: Robert Griesemer <g...@golang.org>
    Gerrit-CC: Gopher Robot <go...@golang.org>
    Gerrit-CC: Sean Liao <se...@liao.dev>
    Gerrit-Attention: Ian Lance Taylor <ia...@golang.org>
    Gerrit-Attention: Brad Fitzpatrick <brad...@golang.org>
    Gerrit-Attention: Robert Griesemer <g...@golang.org>
    Gerrit-Attention: Chencheng Jiang <dorb...@gmail.com>
    Gerrit-Comment-Date: Fri, 17 Jul 2026 21:58:08 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    unsatisfied_requirement
    open
    diffy

    Chencheng Jiang (Gerrit)

    unread,
    7:55 PM (3 hours ago) 7:55 PM
    to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
    Attention needed from Brad Fitzpatrick, Chencheng Jiang, Ian Lance Taylor and Robert Griesemer

    Chencheng Jiang uploaded new patchset

    Chencheng Jiang uploaded patch set #2 to this change.
    Open in Gerrit

    Related details

    Attention is currently required from:
    • Brad Fitzpatrick
    • Chencheng Jiang
    • Ian Lance Taylor
    • Robert Griesemer
    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: I4fc0cff5808169b562e156eafa038450c2fe2d50
    Gerrit-Change-Number: 793620
    Gerrit-PatchSet: 2
    unsatisfied_requirement
    open
    diffy

    Chencheng Jiang (Gerrit)

    unread,
    7:55 PM (3 hours ago) 7:55 PM
    to goph...@pubsubhelper.golang.org, Brad Fitzpatrick, Ian Lance Taylor, Robert Griesemer, Gopher Robot, golang-co...@googlegroups.com
    Attention needed from Brad Fitzpatrick, Ian Lance Taylor, Robert Griesemer and Sean Liao

    Chencheng Jiang added 2 comments

    Commit Message
    Line 27, Patchset 1:bytes/TrimSpace/JustNonASCII 63.3ns 33.2ns -47.5%
    Sean Liao . resolved
    Chencheng Jiang

    Done

    File src/internal/stringslite/strings.go
    Line 118, Patchset 1: if r <= ' ' {
    Sean Liao . resolved

    can this just be one big switch block?
    the ranges aren't that big either.
    and comment on the hex codes what those runes are.

    Chencheng Jiang

    Done

    Open in Gerrit

    Related details

    Attention is currently required from:
    • Brad Fitzpatrick
    • Ian Lance Taylor
    • Robert Griesemer
    • 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: I4fc0cff5808169b562e156eafa038450c2fe2d50
      Gerrit-Change-Number: 793620
      Gerrit-PatchSet: 2
      Gerrit-Owner: Chencheng Jiang <dorb...@gmail.com>
      Gerrit-Reviewer: Brad Fitzpatrick <brad...@golang.org>
      Gerrit-Reviewer: Ian Lance Taylor <ia...@golang.org>
      Gerrit-Reviewer: Robert Griesemer <g...@golang.org>
      Gerrit-CC: Gopher Robot <go...@golang.org>
      Gerrit-CC: Sean Liao <se...@liao.dev>
      Gerrit-Attention: Sean Liao <se...@liao.dev>
      Gerrit-Attention: Ian Lance Taylor <ia...@golang.org>
      Gerrit-Attention: Brad Fitzpatrick <brad...@golang.org>
      Gerrit-Attention: Robert Griesemer <g...@golang.org>
      Gerrit-Comment-Date: Fri, 17 Jul 2026 23:55:47 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: No
      Comment-In-Reply-To: Sean Liao <se...@liao.dev>
      unsatisfied_requirement
      satisfied_requirement
      open
      diffy
      Reply all
      Reply to author
      Forward
      0 new messages