Commit a0252775 authored by Keith Randall's avatar Keith Randall

bytes,strings: in generic Index, use mix of IndexByte and Rabin-Karp

Use IndexByte first, as it allows us to skip lots of bytes quickly.
If IndexByte is generating a lot of false positives, switch over to Rabin-Karp.

Experiments for ppc64le
bytes:
name                             old time/op  new time/op  delta
IndexPeriodic/IndexPeriodic2-2   1.12ms ± 0%  0.18ms ± 0%  -83.54%  (p=0.000 n=10+9)
IndexPeriodic/IndexPeriodic4-2    635µs ± 0%   184µs ± 0%  -71.06%  (p=0.000 n=9+9)
IndexPeriodic/IndexPeriodic8-2    289µs ± 0%   184µs ± 0%  -36.51%  (p=0.000 n=10+9)
IndexPeriodic/IndexPeriodic16-2   133µs ± 0%   183µs ± 0%  +37.68%  (p=0.000 n=10+9)
IndexPeriodic/IndexPeriodic32-2  68.3µs ± 0%  70.2µs ± 0%   +2.76%  (p=0.000 n=10+10)
IndexPeriodic/IndexPeriodic64-2  35.8µs ± 0%  36.6µs ± 0%   +2.17%  (p=0.000 n=8+10)

strings:
name                             old time/op  new time/op  delta
IndexPeriodic/IndexPeriodic2-2    184µs ± 0%   184µs ± 0%   +0.11%  (p=0.029 n=4+4)
IndexPeriodic/IndexPeriodic4-2    184µs ± 0%   184µs ± 0%     ~     (p=0.886 n=4+4)
IndexPeriodic/IndexPeriodic8-2    184µs ± 0%   184µs ± 0%     ~     (p=0.486 n=4+4)
IndexPeriodic/IndexPeriodic16-2   185µs ± 1%   184µs ± 0%     ~     (p=0.343 n=4+4)
IndexPeriodic/IndexPeriodic32-2   184µs ± 0%    69µs ± 0%  -62.37%  (p=0.029 n=4+4)
IndexPeriodic/IndexPeriodic64-2   184µs ± 0%    37µs ± 0%  -80.17%  (p=0.029 n=4+4)

Fixes #22578

Change-Id: If2a4d8554cb96bfd699b58149d13ac294615f8b8
Reviewed-on: https://go-review.googlesource.com/76070Reviewed-by: 's avatarAlberto Donizetti <alb.donizetti@gmail.com>
parent 0ffe90b5
...@@ -815,3 +815,46 @@ func EqualFold(s, t []byte) bool { ...@@ -815,3 +815,46 @@ func EqualFold(s, t []byte) bool {
// One string is empty. Are both? // One string is empty. Are both?
return len(s) == len(t) return len(s) == len(t)
} }
func indexRabinKarp(s, sep []byte) int {
// Rabin-Karp search
hashsep, pow := hashStr(sep)
n := len(sep)
var h uint32
for i := 0; i < n; i++ {
h = h*primeRK + uint32(s[i])
}
if h == hashsep && Equal(s[:n], sep) {
return 0
}
for i := n; i < len(s); {
h *= primeRK
h += uint32(s[i])
h -= pow * uint32(s[i-n])
i++
if h == hashsep && Equal(s[i-n:i], sep) {
return i - n
}
}
return -1
}
// primeRK is the prime base used in Rabin-Karp algorithm.
const primeRK = 16777619
// hashStr returns the hash and the appropriate multiplicative
// factor for use in Rabin-Karp algorithm.
func hashStr(sep []byte) (uint32, uint32) {
hash := uint32(0)
for i := 0; i < len(sep); i++ {
hash = hash*primeRK + uint32(sep[i])
}
var pow, sq uint32 = 1, primeRK
for i := len(sep); i > 0; i >>= 1 {
if i&1 != 0 {
pow *= sq
}
sq *= sq
}
return hash, pow
}
...@@ -75,25 +75,7 @@ func Index(s, sep []byte) int { ...@@ -75,25 +75,7 @@ func Index(s, sep []byte) int {
} }
return -1 return -1
} }
// Rabin-Karp search return indexRabinKarp(s, sep)
hashsep, pow := hashStr(sep)
var h uint32
for i := 0; i < n; i++ {
h = h*primeRK + uint32(s[i])
}
if h == hashsep && Equal(s[:n], sep) {
return 0
}
for i := n; i < len(s); {
h *= primeRK
h += uint32(s[i])
h -= pow * uint32(s[i-n])
i++
if h == hashsep && Equal(s[i-n:i], sep) {
return i - n
}
}
return -1
} }
// Count counts the number of non-overlapping instances of sep in s. // Count counts the number of non-overlapping instances of sep in s.
...@@ -104,23 +86,3 @@ func Count(s, sep []byte) int { ...@@ -104,23 +86,3 @@ func Count(s, sep []byte) int {
} }
return countGeneric(s, sep) return countGeneric(s, sep)
} }
// primeRK is the prime base used in Rabin-Karp algorithm.
const primeRK = 16777619
// hashStr returns the hash and the appropriate multiplicative
// factor for use in Rabin-Karp algorithm.
func hashStr(sep []byte) (uint32, uint32) {
hash := uint32(0)
for i := 0; i < len(sep); i++ {
hash = hash*primeRK + uint32(sep[i])
}
var pow, sq uint32 = 1, primeRK
for i := len(sep); i > 0; i >>= 1 {
if i&1 != 0 {
pow *= sq
}
sq *= sq
}
return hash, pow
}
...@@ -6,23 +6,25 @@ ...@@ -6,23 +6,25 @@
package bytes package bytes
// TODO: implements short string optimization on non amd64 platforms
// and get rid of bytes_amd64.go
// Index returns the index of the first instance of sep in s, or -1 if sep is not present in s. // Index returns the index of the first instance of sep in s, or -1 if sep is not present in s.
func Index(s, sep []byte) int { func Index(s, sep []byte) int {
n := len(sep) n := len(sep)
if n == 0 { switch {
case n == 0:
return 0 return 0
} case n == 1:
if n > len(s) { return IndexByte(s, sep[0])
case n == len(s):
if Equal(sep, s) {
return 0
}
return -1
case n > len(s):
return -1 return -1
} }
c := sep[0] c := sep[0]
if n == 1 {
return IndexByte(s, c)
}
i := 0 i := 0
fails := 0
t := s[:len(s)-n+1] t := s[:len(s)-n+1]
for i < len(t) { for i < len(t) {
if t[i] != c { if t[i] != c {
...@@ -36,6 +38,22 @@ func Index(s, sep []byte) int { ...@@ -36,6 +38,22 @@ func Index(s, sep []byte) int {
return i return i
} }
i++ i++
fails++
if fails >= 4+i>>4 && i < len(t) {
// Give up on IndexByte, it isn't skipping ahead
// far enough to be better than Rabin-Karp.
// Experiments (using IndexPeriodic) suggest
// the cutover is about 16 byte skips.
// TODO: if large prefixes of sep are matching
// we should cutover at even larger average skips,
// because Equal becomes that much more expensive.
// This code does not take that effect into account.
j := indexRabinKarp(s[i:], sep)
if j < 0 {
return -1
}
return i + j
}
} }
return -1 return -1
} }
......
...@@ -76,25 +76,7 @@ func Index(s, sep []byte) int { ...@@ -76,25 +76,7 @@ func Index(s, sep []byte) int {
} }
return -1 return -1
} }
// Rabin-Karp search return indexRabinKarp(s, sep)
hashsep, pow := hashStr(sep)
var h uint32
for i := 0; i < n; i++ {
h = h*primeRK + uint32(s[i])
}
if h == hashsep && Equal(s[:n], sep) {
return 0
}
for i := n; i < len(s); {
h *= primeRK
h += uint32(s[i])
h -= pow * uint32(s[i-n])
i++
if h == hashsep && Equal(s[i-n:i], sep) {
return i - n
}
}
return -1
} }
// Count counts the number of non-overlapping instances of sep in s. // Count counts the number of non-overlapping instances of sep in s.
...@@ -102,23 +84,3 @@ func Index(s, sep []byte) int { ...@@ -102,23 +84,3 @@ func Index(s, sep []byte) int {
func Count(s, sep []byte) int { func Count(s, sep []byte) int {
return countGeneric(s, sep) return countGeneric(s, sep)
} }
// primeRK is the prime base used in Rabin-Karp algorithm.
const primeRK = 16777619
// hashStr returns the hash and the appropriate multiplicative
// factor for use in Rabin-Karp algorithm.
func hashStr(sep []byte) (uint32, uint32) {
hash := uint32(0)
for i := 0; i < len(sep); i++ {
hash = hash*primeRK + uint32(sep[i])
}
var pow, sq uint32 = 1, primeRK
for i := len(sep); i > 0; i >>= 1 {
if i&1 != 0 {
pow *= sq
}
sq *= sq
}
return hash, pow
}
...@@ -139,6 +139,9 @@ var indexTests = []BinOpTest{ ...@@ -139,6 +139,9 @@ var indexTests = []BinOpTest{
{"barfoobarfooyyyzzzyyyzzzyyyzzzyyyxxxzzzyyy", "x", 33}, {"barfoobarfooyyyzzzyyyzzzyyyzzzyyyxxxzzzyyy", "x", 33},
{"foofyfoobarfoobar", "y", 4}, {"foofyfoobarfoobar", "y", 4},
{"oooooooooooooooooooooo", "r", -1}, {"oooooooooooooooooooooo", "r", -1},
// test fallback to Rabin-Karp.
{"oxoxoxoxoxoxoxoxoxoxoxoy", "oy", 22},
{"oxoxoxoxoxoxoxoxoxoxoxox", "oy", -1},
} }
var lastIndexTests = []BinOpTest{ var lastIndexTests = []BinOpTest{
...@@ -1730,3 +1733,18 @@ func BenchmarkTrimASCII(b *testing.B) { ...@@ -1730,3 +1733,18 @@ func BenchmarkTrimASCII(b *testing.B) {
} }
} }
} }
func BenchmarkIndexPeriodic(b *testing.B) {
key := []byte{1, 1}
for _, skip := range [...]int{2, 4, 8, 16, 32, 64} {
b.Run(fmt.Sprintf("IndexPeriodic%d", skip), func(b *testing.B) {
buf := make([]byte, 1<<16)
for i := 0; i < len(buf); i += skip {
buf[i] = 1
}
for i := 0; i < b.N; i++ {
Index(buf, key)
}
})
}
}
...@@ -918,3 +918,27 @@ func EqualFold(s, t string) bool { ...@@ -918,3 +918,27 @@ func EqualFold(s, t string) bool {
// One string is empty. Are both? // One string is empty. Are both?
return s == t return s == t
} }
func indexRabinKarp(s, substr string) int {
// Rabin-Karp search
hashss, pow := hashStr(substr)
n := len(substr)
var h uint32
for i := 0; i < n; i++ {
h = h*primeRK + uint32(s[i])
}
if h == hashss && s[:n] == substr {
return 0
}
for i := n; i < len(s); {
h *= primeRK
h += uint32(s[i])
h -= pow * uint32(s[i-n])
i++
if h == hashss && s[i-n:i] == substr {
return i - n
}
}
return -1
}
...@@ -75,25 +75,7 @@ func Index(s, substr string) int { ...@@ -75,25 +75,7 @@ func Index(s, substr string) int {
} }
return -1 return -1
} }
// Rabin-Karp search return indexRabinKarp(s, substr)
hashss, pow := hashStr(substr)
var h uint32
for i := 0; i < n; i++ {
h = h*primeRK + uint32(s[i])
}
if h == hashss && s[:n] == substr {
return 0
}
for i := n; i < len(s); {
h *= primeRK
h += uint32(s[i])
h -= pow * uint32(s[i-n])
i++
if h == hashss && s[i-n:i] == substr {
return i - n
}
}
return -1
} }
// Count counts the number of non-overlapping instances of substr in s. // Count counts the number of non-overlapping instances of substr in s.
......
...@@ -25,22 +25,30 @@ func Index(s, substr string) int { ...@@ -25,22 +25,30 @@ func Index(s, substr string) int {
case n > len(s): case n > len(s):
return -1 return -1
} }
// Rabin-Karp search c := substr[0]
hashss, pow := hashStr(substr) i := 0
var h uint32 t := s[:len(s)-n+1]
for i := 0; i < n; i++ { fails := 0
h = h*primeRK + uint32(s[i]) for i < len(t) {
} if t[i] != c {
if h == hashss && s[:n] == substr { o := IndexByte(t[i:], c)
return 0 if o < 0 {
} return -1
for i := n; i < len(s); { }
h *= primeRK i += o
h += uint32(s[i]) }
h -= pow * uint32(s[i-n]) if s[i:i+n] == substr {
return i
}
i++ i++
if h == hashss && s[i-n:i] == substr { fails++
return i - n if fails >= 4+i>>4 && i < len(t) {
// See comment in ../bytes/bytes_generic.go.
j := indexRabinKarp(s[i:], substr)
if j < 0 {
return -1
}
return i + j
} }
} }
return -1 return -1
......
...@@ -76,25 +76,7 @@ func Index(s, substr string) int { ...@@ -76,25 +76,7 @@ func Index(s, substr string) int {
} }
return -1 return -1
} }
// Rabin-Karp search return indexRabinKarp(s, substr)
hashss, pow := hashStr(substr)
var h uint32
for i := 0; i < n; i++ {
h = h*primeRK + uint32(s[i])
}
if h == hashss && s[:n] == substr {
return 0
}
for i := n; i < len(s); {
h *= primeRK
h += uint32(s[i])
h -= pow * uint32(s[i-n])
i++
if h == hashss && s[i-n:i] == substr {
return i - n
}
}
return -1
} }
// Count counts the number of non-overlapping instances of substr in s. // Count counts the number of non-overlapping instances of substr in s.
......
...@@ -125,6 +125,9 @@ var indexTests = []IndexTest{ ...@@ -125,6 +125,9 @@ var indexTests = []IndexTest{
{"xx012345678901234567890123456789012345678901234567890123456789012"[:41], "0123456789012345678901234567890123456789", -1}, {"xx012345678901234567890123456789012345678901234567890123456789012"[:41], "0123456789012345678901234567890123456789", -1},
{"xx012345678901234567890123456789012345678901234567890123456789012", "0123456789012345678901234567890123456xxx", -1}, {"xx012345678901234567890123456789012345678901234567890123456789012", "0123456789012345678901234567890123456xxx", -1},
{"xx0123456789012345678901234567890123456789012345678901234567890120123456789012345678901234567890123456xxx", "0123456789012345678901234567890123456xxx", 65}, {"xx0123456789012345678901234567890123456789012345678901234567890120123456789012345678901234567890123456xxx", "0123456789012345678901234567890123456xxx", 65},
// test fallback to Rabin-Karp.
{"oxoxoxoxoxoxoxoxoxoxoxoy", "oy", 22},
{"oxoxoxoxoxoxoxoxoxoxoxox", "oy", -1},
} }
var lastIndexTests = []IndexTest{ var lastIndexTests = []IndexTest{
...@@ -1641,3 +1644,15 @@ func BenchmarkTrimASCII(b *testing.B) { ...@@ -1641,3 +1644,15 @@ func BenchmarkTrimASCII(b *testing.B) {
} }
} }
} }
func BenchmarkIndexPeriodic(b *testing.B) {
key := "aa"
for _, skip := range [...]int{2, 4, 8, 16, 32, 64} {
b.Run(fmt.Sprintf("IndexPeriodic%d", skip), func(b *testing.B) {
s := Repeat("a"+Repeat(" ", skip-1), 1<<16/skip)
for i := 0; i < b.N; i++ {
Index(s, key)
}
})
}
}
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