diff --git a/src/bytes/bytes.go b/src/bytes/bytes.go index 119b1f62b1a..e2e5d5fda75 100644 --- a/src/bytes/bytes.go +++ b/src/bytes/bytes.go @@ -578,8 +578,8 @@ func Map(mapping func(r rune) rune, s []byte) []byte { // Repeat returns a new byte slice consisting of count copies of b. // -// It panics if count is negative or if -// the result of (len(b) * count) overflows. +// It panics if count is negative or if the result of (len(b) * count) +// overflows. func Repeat(b []byte, count int) []byte { if count == 0 { return []byte{} @@ -587,18 +587,45 @@ func Repeat(b []byte, count int) []byte { // Since we cannot return an error on overflow, // we should panic if the repeat will generate // an overflow. - // See Issue golang.org/issue/16237. + // See golang.org/issue/16237. if count < 0 { panic("bytes: negative Repeat count") } else if len(b)*count/count != len(b) { panic("bytes: Repeat count causes overflow") } - nb := make([]byte, len(b)*count) + if len(b) == 0 { + return []byte{} + } + + n := len(b) * count + + // Past a certain chunk size it is counterproductive to use + // larger chunks as the source of the write, as when the source + // is too large we are basically just thrashing the CPU D-cache. + // So if the result length is larger than an empirically-found + // limit (8KB), we stop growing the source string once the limit + // is reached and keep reusing the same source string - that + // should therefore be always resident in the L1 cache - until we + // have completed the construction of the result. + // This yields significant speedups (up to +100%) in cases where + // the result length is large (roughly, over L2 cache size). + const chunkLimit = 8 * 1024 + chunkMax := n + if chunkMax > chunkLimit { + chunkMax = chunkLimit / len(b) * len(b) + if chunkMax == 0 { + chunkMax = len(b) + } + } + nb := make([]byte, n) bp := copy(nb, b) for bp < len(nb) { - copy(nb[bp:], nb[:bp]) - bp *= 2 + chunk := bp + if chunk > chunkMax { + chunk = chunkMax + } + bp += copy(nb[bp:], nb[:chunk]) } return nb } diff --git a/src/bytes/bytes_test.go b/src/bytes/bytes_test.go index 7263af3ed01..f58f18c461b 100644 --- a/src/bytes/bytes_test.go +++ b/src/bytes/bytes_test.go @@ -1159,6 +1159,8 @@ type RepeatTest struct { count int } +var longString = "a" + string(make([]byte, 1<<16)) + "z" + var RepeatTests = []RepeatTest{ {"", "", 0}, {"", "", 1}, @@ -1167,6 +1169,9 @@ var RepeatTests = []RepeatTest{ {"-", "-", 1}, {"-", "----------", 10}, {"abc ", "abc abc abc ", 3}, + // Tests for results over the chunkLimit + {string(rune(0)), string(make([]byte, 1<<16)), 1 << 16}, + {longString, longString + longString, 2}, } func TestRepeat(t *testing.T) { @@ -2048,6 +2053,25 @@ func BenchmarkRepeat(b *testing.B) { } } +func BenchmarkRepeatLarge(b *testing.B) { + s := Repeat([]byte("@"), 8*1024) + for j := 8; j <= 30; j++ { + for _, k := range []int{1, 16, 4097} { + s := s[:k] + n := (1 << j) / k + if n == 0 { + continue + } + b.Run(fmt.Sprintf("%d/%d", 1< chunkLimit { + chunkMax = chunkLimit / len(s) * len(s) + if chunkMax == 0 { + chunkMax = len(s) + } + } + var b Builder b.Grow(n) b.WriteString(s) for b.Len() < n { - if b.Len() <= n/2 { - b.WriteString(b.String()) - } else { - b.WriteString(b.String()[:n-b.Len()]) - break + chunk := n - b.Len() + if chunk > b.Len() { + chunk = b.Len() } + if chunk > chunkMax { + chunk = chunkMax + } + b.WriteString(b.String()[:chunk]) } return b.String() } diff --git a/src/strings/strings_test.go b/src/strings/strings_test.go index 210bd9e44b4..27489c2d165 100644 --- a/src/strings/strings_test.go +++ b/src/strings/strings_test.go @@ -1105,6 +1105,8 @@ func TestCaseConsistency(t *testing.T) { */ } +var longString = "a" + string(make([]byte, 1<<16)) + "z" + var RepeatTests = []struct { in, out string count int @@ -1116,6 +1118,9 @@ var RepeatTests = []struct { {"-", "-", 1}, {"-", "----------", 10}, {"abc ", "abc abc abc ", 3}, + // Tests for results over the chunkLimit + {string(rune(0)), string(make([]byte, 1<<16)), 1 << 16}, + {longString, longString + longString, 2}, } func TestRepeat(t *testing.T) { @@ -1885,6 +1890,25 @@ func BenchmarkRepeat(b *testing.B) { } } +func BenchmarkRepeatLarge(b *testing.B) { + s := Repeat("@", 8*1024) + for j := 8; j <= 30; j++ { + for _, k := range []int{1, 16, 4097} { + s := s[:k] + n := (1 << j) / k + if n == 0 { + continue + } + b.Run(fmt.Sprintf("%d/%d", 1<