0
1
mirror of https://github.com/golang/go synced 2025-05-27 15:10:40 +00:00

strings: speed up Replace

Add benchmarks for Replace.

The length of parameter old does not change. Move the corresponding length
check outside the loop. Use range-over-int loops where possible.

Some benchmark results (no changes to allocations):

goos: darwin
goarch: amd64
pkg: strings
cpu: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
                                 │     old      │                new                 │
                                 │    sec/op    │   sec/op     vs base               │
Replace/"hello"_"l"_"L"_0-8         6.102n ± 6%   6.139n ± 5%       ~ (p=0.644 n=20)
Replace/"hello"_"l"_"L"_-1-8        62.81n ± 1%   63.30n ± 1%       ~ (p=0.098 n=20)
Replace/"hello"_"x"_"X"_-1-8        12.19n ± 0%   12.49n ± 0%  +2.50% (p=0.000 n=20)
Replace/""_"x"_"X"_-1-8             12.20n ± 0%   12.51n ± 0%  +2.58% (p=0.000 n=20)
Replace/"radar"_"r"_"<r>"_-1-8      69.65n ± 0%   66.57n ± 0%  -4.43% (p=0.000 n=20)
Replace/""_""_"<>"_-1-8             39.54n ± 1%   35.84n ± 1%  -9.34% (p=0.000 n=20)
Replace/"banana"_"a"_"<>"_-1-8      79.95n ± 0%   79.21n ± 0%  -0.93% (p=0.000 n=20)
Replace/"banana"_"a"_"<>"_1-8       48.67n ± 1%   49.45n ± 0%  +1.60% (p=0.000 n=20)
Replace/"banana"_"a"_"<>"_1000-8    80.28n ± 1%   79.52n ± 0%  -0.95% (p=0.000 n=20)
Replace/"banana"_"an"_"<>"_-1-8     82.89n ± 1%   84.62n ± 1%  +2.09% (p=0.000 n=20)
Replace/"banana"_"ana"_"<>"_-1-8    56.45n ± 1%   57.41n ± 1%  +1.69% (p=0.000 n=20)
Replace/"banana"_""_"<>"_-1-8       114.5n ± 1%   104.8n ± 0%  -8.52% (p=0.000 n=20)
Replace/"banana"_""_"<>"_10-8       114.5n ± 0%   104.8n ± 0%  -8.43% (p=0.000 n=20)
Replace/"banana"_""_"<>"_6-8       104.00n ± 0%   95.43n ± 0%  -8.24% (p=0.000 n=20)
Replace/"banana"_""_"<>"_5-8        91.85n ± 1%   83.68n ± 1%  -8.89% (p=0.000 n=20)
Replace/"banana"_""_"<>"_1-8        43.73n ± 1%   40.17n ± 0%  -8.13% (p=0.000 n=20)
Replace/"banana"_"a"_"a"_-1-8       4.410n ± 0%   4.443n ± 0%  +0.76% (p=0.000 n=20)
Replace/"banana"_"a"_"a"_1-8        4.395n ± 0%   4.423n ± 1%  +0.64% (p=0.022 n=20)
Replace/"☺☻☹"_""_"<>"_-1-8          98.58n ± 0%   91.66n ± 0%  -7.02% (p=0.000 n=20)
geomean                             39.72n        38.59n       -2.83%

Change-Id: Ia0c7798b24d95d7c98b488a6d4ce7e78de76db9d
GitHub-Last-Rev: 644fe36dcb
GitHub-Pull-Request: 
Reviewed-on: https://go-review.googlesource.com/c/go/+/657935
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Jorropo <jorropo.pgm@gmail.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Auto-Submit: Jorropo <jorropo.pgm@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Julien Cretel
2025-03-19 20:07:38 +00:00
committed by Gopher Robot
parent 19b6f01161
commit 2fb5610c3a
2 changed files with 30 additions and 11 deletions

@ -1158,19 +1158,22 @@ func Replace(s, old, new string, n int) string {
var b Builder
b.Grow(len(s) + n*(len(new)-len(old)))
start := 0
for i := 0; i < n; i++ {
j := start
if len(old) == 0 {
if i > 0 {
_, wid := utf8.DecodeRuneInString(s[start:])
j += wid
}
} else {
j += Index(s[start:], old)
if len(old) > 0 {
for range n {
j := start + Index(s[start:], old)
b.WriteString(s[start:j])
b.WriteString(new)
start = j + len(old)
}
b.WriteString(s[start:j])
} else { // len(old) == 0
b.WriteString(new)
start = j + len(old)
for range n - 1 {
_, wid := utf8.DecodeRuneInString(s[start:])
j := start + wid
b.WriteString(s[start:j])
b.WriteString(new)
start = j
}
}
b.WriteString(s[start:])
return b.String()

@ -1473,6 +1473,10 @@ var ReplaceTests = []struct {
func TestReplace(t *testing.T) {
for _, tt := range ReplaceTests {
allocs := testing.AllocsPerRun(10, func() { Replace(tt.in, tt.old, tt.new, tt.n) })
if allocs > 1 {
t.Errorf("Replace(%q, %q, %q, %d) allocates %.2f objects", tt.in, tt.old, tt.new, tt.n, allocs)
}
if s := Replace(tt.in, tt.old, tt.new, tt.n); s != tt.out {
t.Errorf("Replace(%q, %q, %q, %d) = %q, want %q", tt.in, tt.old, tt.new, tt.n, s, tt.out)
}
@ -1531,6 +1535,18 @@ func FuzzReplace(f *testing.F) {
})
}
func BenchmarkReplace(b *testing.B) {
for _, tt := range ReplaceTests {
desc := fmt.Sprintf("%q %q %q %d", tt.in, tt.old, tt.new, tt.n)
b.Run(desc, func(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
Replace(tt.in, tt.old, tt.new, tt.n)
}
})
}
}
var TitleTests = []struct {
in, out string
}{