Commit 3142861f authored by Rui Ueyama's avatar Rui Ueyama

strings: use sync.Pool to cache buffer

benchmark                         old ns/op    new ns/op    delta
BenchmarkByteReplacerWriteString       3596         3094  -13.96%

benchmark                        old allocs   new allocs    delta
BenchmarkByteReplacerWriteString          1            0  -100.00%

LGTM=dvyukov
R=bradfitz, dave, dvyukov
CC=golang-codereviews
https://golang.org/cl/101330053
parent 9bfb66e9
...@@ -4,7 +4,10 @@ ...@@ -4,7 +4,10 @@
package strings package strings
import "io" import (
"io"
"sync"
)
// A Replacer replaces a list of strings with replacements. // A Replacer replaces a list of strings with replacements.
type Replacer struct { type Replacer struct {
...@@ -451,27 +454,31 @@ func (r *byteReplacer) Replace(s string) string { ...@@ -451,27 +454,31 @@ func (r *byteReplacer) Replace(s string) string {
return string(buf) return string(buf)
} }
func (r *byteReplacer) WriteString(w io.Writer, s string) (n int, err error) { var bufferPool = sync.Pool{
// TODO(bradfitz): use io.WriteString with slices of s, avoiding allocation. New: func() interface{} {
bufsize := 32 << 10 b := make([]byte, 4096)
if len(s) < bufsize { return &b
bufsize = len(s) },
} }
buf := make([]byte, bufsize)
func (r *byteReplacer) WriteString(w io.Writer, s string) (n int, err error) {
bp := bufferPool.Get().(*[]byte)
buf := *bp
for len(s) > 0 { for len(s) > 0 {
ncopy := copy(buf, s[:]) ncopy := copy(buf, s)
s = s[ncopy:]
for i, b := range buf[:ncopy] { for i, b := range buf[:ncopy] {
buf[i] = r.new[b] buf[i] = r.new[b]
} }
wn, err := w.Write(buf[:ncopy]) s = s[ncopy:]
var wn int
wn, err = w.Write(buf[:ncopy])
n += wn n += wn
if err != nil { if err != nil {
return n, err break
} }
} }
return n, nil bufferPool.Put(bp)
return
} }
// byteStringReplacer is the implementation that's used when all the // byteStringReplacer is the implementation that's used when all the
......
...@@ -308,20 +308,21 @@ func TestReplacer(t *testing.T) { ...@@ -308,20 +308,21 @@ func TestReplacer(t *testing.T) {
} }
} }
var algorithmTestCases = []struct {
r *Replacer
want string
}{
{capitalLetters, "*strings.byteReplacer"},
{htmlEscaper, "*strings.byteStringReplacer"},
{NewReplacer("12", "123"), "*strings.singleStringReplacer"},
{NewReplacer("1", "12"), "*strings.byteStringReplacer"},
{NewReplacer("", "X"), "*strings.genericReplacer"},
{NewReplacer("a", "1", "b", "12", "cde", "123"), "*strings.genericReplacer"},
}
// TestPickAlgorithm tests that NewReplacer picks the correct algorithm. // TestPickAlgorithm tests that NewReplacer picks the correct algorithm.
func TestPickAlgorithm(t *testing.T) { func TestPickAlgorithm(t *testing.T) {
testCases := []struct { for i, tc := range algorithmTestCases {
r *Replacer
want string
}{
{capitalLetters, "*strings.byteReplacer"},
{htmlEscaper, "*strings.byteStringReplacer"},
{NewReplacer("12", "123"), "*strings.singleStringReplacer"},
{NewReplacer("1", "12"), "*strings.byteStringReplacer"},
{NewReplacer("", "X"), "*strings.genericReplacer"},
{NewReplacer("a", "1", "b", "12", "cde", "123"), "*strings.genericReplacer"},
}
for i, tc := range testCases {
got := fmt.Sprintf("%T", tc.r.Replacer()) got := fmt.Sprintf("%T", tc.r.Replacer())
if got != tc.want { if got != tc.want {
t.Errorf("%d. algorithm = %s, want %s", i, got, tc.want) t.Errorf("%d. algorithm = %s, want %s", i, got, tc.want)
...@@ -329,6 +330,23 @@ func TestPickAlgorithm(t *testing.T) { ...@@ -329,6 +330,23 @@ func TestPickAlgorithm(t *testing.T) {
} }
} }
type errWriter struct{}
func (errWriter) Write(p []byte) (n int, err error) {
return 0, fmt.Errorf("unwritable")
}
// TestWriteStringError tests that WriteString returns an error
// received from the underlying io.Writer.
func TestWriteStringError(t *testing.T) {
for i, tc := range algorithmTestCases {
n, err := tc.r.WriteString(errWriter{}, "abc")
if n != 0 || err == nil || err.Error() != "unwritable" {
t.Errorf("%d. WriteStringError = %d, %v, want 0, unwritable", i, n, err)
}
}
}
// TestGenericTrieBuilding verifies the structure of the generated trie. There // TestGenericTrieBuilding verifies the structure of the generated trie. There
// is one node per line, and the key ending with the current line is in the // is one node per line, and the key ending with the current line is in the
// trie if it ends with a "+". // trie if it ends with a "+".
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment