return m
}
+func (m *machine) init(ncap int) {
+ for _, t := range m.pool {
+ t.cap = t.cap[:ncap]
+ }
+ m.matchcap = m.matchcap[:ncap]
+}
+
// alloc allocates a new thread with the given instruction.
// It uses the free pool if possible.
func (m *machine) alloc(i *syntax.Inst) *thread {
m.pool = m.pool[:n-1]
} else {
t = new(thread)
- t.cap = make([]int, cap(m.matchcap))
+ t.cap = make([]int, len(m.matchcap), cap(m.matchcap))
}
- t.cap = t.cap[:len(m.matchcap)]
t.inst = i
return t
}
if len(m.matchcap) > 0 {
m.matchcap[0] = pos
}
- m.add(runq, uint32(m.p.Start), pos, m.matchcap, flag)
+ m.add(runq, uint32(m.p.Start), pos, m.matchcap, flag, nil)
}
flag = syntax.EmptyOpContext(rune, rune1)
m.step(runq, nextq, pos, pos+width, rune, flag)
func (m *machine) clear(q *queue) {
for _, d := range q.dense {
if d.t != nil {
- m.free(d.t)
+ // m.free(d.t)
+ m.pool = append(m.pool, d.t)
}
}
q.dense = q.dense[:0]
continue
}
if longest && m.matched && len(t.cap) > 0 && m.matchcap[0] < t.cap[0] {
- m.free(t)
+ // m.free(t)
+ m.pool = append(m.pool, t)
continue
}
i := t.inst
+ add := false
switch i.Op {
default:
panic("bad inst")
// First-match mode: cut off all lower-priority threads.
for _, d := range runq.dense[j+1:] {
if d.t != nil {
- m.free(d.t)
+ // m.free(d.t)
+ m.pool = append(m.pool, d.t)
}
}
runq.dense = runq.dense[:0]
m.matched = true
case syntax.InstRune:
- if i.MatchRune(c) {
- m.add(nextq, i.Out, nextPos, t.cap, nextCond)
- }
+ add = i.MatchRune(c)
+ case syntax.InstRune1:
+ add = c == i.Rune[0]
+ case syntax.InstRuneAny:
+ add = true
+ case syntax.InstRuneAnyNotNL:
+ add = c != '\n'
+ }
+ if add {
+ t = m.add(nextq, i.Out, nextPos, t.cap, nextCond, t)
+ }
+ if t != nil {
+ // m.free(t)
+ m.pool = append(m.pool, t)
}
- m.free(t)
}
runq.dense = runq.dense[:0]
}
// It also recursively adds an entry for all instructions reachable from pc by following
// empty-width conditions satisfied by cond. pos gives the current position
// in the input.
-func (m *machine) add(q *queue, pc uint32, pos int, cap []int, cond syntax.EmptyOp) {
+func (m *machine) add(q *queue, pc uint32, pos int, cap []int, cond syntax.EmptyOp, t *thread) *thread {
if pc == 0 {
- return
+ return t
}
if j := q.sparse[pc]; j < uint32(len(q.dense)) && q.dense[j].pc == pc {
- return
+ return t
}
j := len(q.dense)
case syntax.InstFail:
// nothing
case syntax.InstAlt, syntax.InstAltMatch:
- m.add(q, i.Out, pos, cap, cond)
- m.add(q, i.Arg, pos, cap, cond)
+ t = m.add(q, i.Out, pos, cap, cond, t)
+ t = m.add(q, i.Arg, pos, cap, cond, t)
case syntax.InstEmptyWidth:
if syntax.EmptyOp(i.Arg)&^cond == 0 {
- m.add(q, i.Out, pos, cap, cond)
+ t = m.add(q, i.Out, pos, cap, cond, t)
}
case syntax.InstNop:
- m.add(q, i.Out, pos, cap, cond)
+ t = m.add(q, i.Out, pos, cap, cond, t)
case syntax.InstCapture:
if int(i.Arg) < len(cap) {
opos := cap[i.Arg]
cap[i.Arg] = pos
- m.add(q, i.Out, pos, cap, cond)
+ m.add(q, i.Out, pos, cap, cond, nil)
cap[i.Arg] = opos
} else {
- m.add(q, i.Out, pos, cap, cond)
+ t = m.add(q, i.Out, pos, cap, cond, t)
+ }
+ case syntax.InstMatch, syntax.InstRune, syntax.InstRune1, syntax.InstRuneAny, syntax.InstRuneAnyNotNL:
+ if t == nil {
+ t = m.alloc(i)
+ } else {
+ t.inst = i
}
- case syntax.InstMatch, syntax.InstRune:
- t := m.alloc(i)
- if len(t.cap) > 0 {
+ if len(cap) > 0 && &t.cap[0] != &cap[0] {
copy(t.cap, cap)
}
d.t = t
+ t = nil
}
+ return t
}
// empty is a non-nil 0-element slice,
// the position of its subexpressions.
func (re *Regexp) doExecute(i input, pos int, ncap int) []int {
m := re.get()
- m.matchcap = m.matchcap[:ncap]
+ m.init(ncap)
if !m.match(i, pos) {
re.put(m)
return nil
"compress/bzip2"
"fmt"
"io"
+ old "old/regexp"
"os"
"path/filepath"
+ "rand"
"regexp/syntax"
"strconv"
"strings"
pos = x
return
}
+
+var text []byte
+
+func makeText(n int) []byte {
+ if len(text) >= n {
+ return text[:n]
+ }
+ text = make([]byte, n)
+ for i := range text {
+ if rand.Intn(30) == 0 {
+ text[i] = '\n'
+ } else {
+ text[i] = byte(rand.Intn(0x7E+1-0x20) + 0x20)
+ }
+ }
+ return text
+}
+
+func benchmark(b *testing.B, re string, n int) {
+ r := MustCompile(re)
+ t := makeText(n)
+ b.ResetTimer()
+ b.SetBytes(int64(n))
+ for i := 0; i < b.N; i++ {
+ if r.Match(t) {
+ panic("match!")
+ }
+ }
+}
+
+func benchold(b *testing.B, re string, n int) {
+ r := old.MustCompile(re)
+ t := makeText(n)
+ b.ResetTimer()
+ b.SetBytes(int64(n))
+ for i := 0; i < b.N; i++ {
+ if r.Match(t) {
+ panic("match!")
+ }
+ }
+}
+
+const (
+ easy0 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ$"
+ easy1 = "A[AB]B[BC]C[CD]D[DE]E[EF]F[FG]G[GH]H[HI]I[IJ]J$"
+ medium = "[XYZ]ABCDEFGHIJKLMNOPQRSTUVWXYZ$"
+ hard = "[ -~]*ABCDEFGHIJKLMNOPQRSTUVWXYZ$"
+ parens = "([ -~])*(A)(B)(C)(D)(E)(F)(G)(H)(I)(J)(K)(L)(M)" +
+ "(N)(O)(P)(Q)(R)(S)(T)(U)(V)(W)(X)(Y)(Z)$"
+)
+
+func BenchmarkMatchEasy0_1K(b *testing.B) { benchmark(b, easy0, 1<<10) }
+func BenchmarkMatchEasy0_1K_Old(b *testing.B) { benchold(b, easy0, 1<<10) }
+func BenchmarkMatchEasy0_1M(b *testing.B) { benchmark(b, easy0, 1<<20) }
+func BenchmarkMatchEasy0_1M_Old(b *testing.B) { benchold(b, easy0, 1<<20) }
+func BenchmarkMatchEasy0_32K(b *testing.B) { benchmark(b, easy0, 32<<10) }
+func BenchmarkMatchEasy0_32K_Old(b *testing.B) { benchold(b, easy0, 32<<10) }
+func BenchmarkMatchEasy0_32M(b *testing.B) { benchmark(b, easy0, 32<<20) }
+func BenchmarkMatchEasy0_32M_Old(b *testing.B) { benchold(b, easy0, 32<<20) }
+func BenchmarkMatchEasy1_1K(b *testing.B) { benchmark(b, easy1, 1<<10) }
+func BenchmarkMatchEasy1_1K_Old(b *testing.B) { benchold(b, easy1, 1<<10) }
+func BenchmarkMatchEasy1_1M(b *testing.B) { benchmark(b, easy1, 1<<20) }
+func BenchmarkMatchEasy1_1M_Old(b *testing.B) { benchold(b, easy1, 1<<20) }
+func BenchmarkMatchEasy1_32K(b *testing.B) { benchmark(b, easy1, 32<<10) }
+func BenchmarkMatchEasy1_32K_Old(b *testing.B) { benchold(b, easy1, 32<<10) }
+func BenchmarkMatchEasy1_32M(b *testing.B) { benchmark(b, easy1, 32<<20) }
+func BenchmarkMatchEasy1_32M_Old(b *testing.B) { benchold(b, easy1, 32<<20) }
+func BenchmarkMatchMedium_1K(b *testing.B) { benchmark(b, medium, 1<<10) }
+func BenchmarkMatchMedium_1K_Old(b *testing.B) { benchold(b, medium, 1<<10) }
+func BenchmarkMatchMedium_1M(b *testing.B) { benchmark(b, medium, 1<<20) }
+func BenchmarkMatchMedium_1M_Old(b *testing.B) { benchold(b, medium, 1<<20) }
+func BenchmarkMatchMedium_32K(b *testing.B) { benchmark(b, medium, 32<<10) }
+func BenchmarkMatchMedium_32K_Old(b *testing.B) { benchold(b, medium, 32<<10) }
+func BenchmarkMatchMedium_32M(b *testing.B) { benchmark(b, medium, 32<<20) }
+func BenchmarkMatchMedium_32M_Old(b *testing.B) { benchold(b, medium, 32<<20) }
+func BenchmarkMatchHard_1K(b *testing.B) { benchmark(b, hard, 1<<10) }
+func BenchmarkMatchHard_1K_Old(b *testing.B) { benchold(b, hard, 1<<10) }
+func BenchmarkMatchHard_1M(b *testing.B) { benchmark(b, hard, 1<<20) }
+func BenchmarkMatchHard_1M_Old(b *testing.B) { benchold(b, hard, 1<<20) }
+func BenchmarkMatchHard_32K(b *testing.B) { benchmark(b, hard, 32<<10) }
+func BenchmarkMatchHard_32K_Old(b *testing.B) { benchold(b, hard, 32<<10) }
+func BenchmarkMatchHard_32M(b *testing.B) { benchmark(b, hard, 32<<20) }
+func BenchmarkMatchHard_32M_Old(b *testing.B) { benchold(b, hard, 32<<20) }
func (i *inputString) step(pos int) (int, int) {
if pos < len(i.str) {
- return utf8.DecodeRuneInString(i.str[pos:len(i.str)])
+ c := i.str[pos]
+ if c < utf8.RuneSelf {
+ return int(c), 1
+ }
+ return utf8.DecodeRuneInString(i.str[pos:])
}
return endOfText, 0
}
func (i *inputBytes) step(pos int) (int, int) {
if pos < len(i.str) {
- return utf8.DecodeRune(i.str[pos:len(i.str)])
+ c := i.str[pos]
+ if c < utf8.RuneSelf {
+ return int(c), 1
+ }
+ return utf8.DecodeRune(i.str[pos:])
}
return endOfText, 0
}
}
i.Arg = uint32(flags)
f.out = patchList(f.i << 1)
+
+ // Special cases for exec machine.
+ switch {
+ case flags&FoldCase == 0 && (len(rune) == 1 || len(rune) == 2 && rune[0] == rune[1]):
+ i.Op = InstRune1
+ case len(rune) == 2 && rune[0] == 0 && rune[1] == unicode.MaxRune:
+ i.Op = InstRuneAny
+ case len(rune) == 4 && rune[0] == 0 && rune[1] == '\n'-1 && rune[2] == '\n'+1 && rune[3] == unicode.MaxRune:
+ i.Op = InstRuneAnyNotNL
+ }
+
return f
}
InstFail
InstNop
InstRune
+ InstRune1
+ InstRuneAny
+ InstRuneAnyNotNL
)
// An EmptyOp specifies a kind or mixture of zero-width assertions.
return i
}
+// op returns i.Op but merges all the Rune special cases into InstRune
+func (i *Inst) op() InstOp {
+ op := i.Op
+ switch op {
+ case InstRune1, InstRuneAny, InstRuneAnyNotNL:
+ op = InstRune
+ }
+ return op
+}
+
// Prefix returns a literal string that all matches for the
// regexp must start with. Complete is true if the prefix
// is the entire match.
i := p.skipNop(uint32(p.Start))
// Avoid allocation of buffer if prefix is empty.
- if i.Op != InstRune || len(i.Rune) != 1 {
+ if i.op() != InstRune || len(i.Rune) != 1 {
return "", i.Op == InstMatch
}
// Have prefix; gather characters.
var buf bytes.Buffer
- for i.Op == InstRune && len(i.Rune) == 1 && Flags(i.Arg)&FoldCase == 0 {
+ for i.op() == InstRune && len(i.Rune) == 1 && Flags(i.Arg)&FoldCase == 0 {
buf.WriteRune(i.Rune[0])
i = p.skipNop(i.Out)
}
bw(b, "/i")
}
bw(b, " -> ", u32(i.Out))
+ case InstRune1:
+ bw(b, "rune1 ", strconv.QuoteToASCII(string(i.Rune)), " -> ", u32(i.Out))
+ case InstRuneAny:
+ bw(b, "any -> ", u32(i.Out))
+ case InstRuneAnyNotNL:
+ bw(b, "anynotnl -> ", u32(i.Out))
}
}
Prog string
}{
{"a", ` 0 fail
- 1* rune "a" -> 2
+ 1* rune1 "a" -> 2
2 match
`},
{"[A-M][n-z]", ` 0 fail
2 match
`},
{"a?", ` 0 fail
- 1 rune "a" -> 3
+ 1 rune1 "a" -> 3
2* alt -> 1, 3
3 match
`},
{"a??", ` 0 fail
- 1 rune "a" -> 3
+ 1 rune1 "a" -> 3
2* alt -> 3, 1
3 match
`},
{"a+", ` 0 fail
- 1* rune "a" -> 2
+ 1* rune1 "a" -> 2
2 alt -> 1, 3
3 match
`},
{"a+?", ` 0 fail
- 1* rune "a" -> 2
+ 1* rune1 "a" -> 2
2 alt -> 3, 1
3 match
`},
{"a*", ` 0 fail
- 1 rune "a" -> 2
+ 1 rune1 "a" -> 2
2* alt -> 1, 3
3 match
`},
{"a*?", ` 0 fail
- 1 rune "a" -> 2
+ 1 rune1 "a" -> 2
2* alt -> 3, 1
3 match
`},
{"a+b+", ` 0 fail
- 1* rune "a" -> 2
+ 1* rune1 "a" -> 2
2 alt -> 1, 3
- 3 rune "b" -> 4
+ 3 rune1 "b" -> 4
4 alt -> 3, 5
5 match
`},
{"(a+)(b+)", ` 0 fail
1* cap 2 -> 2
- 2 rune "a" -> 3
+ 2 rune1 "a" -> 3
3 alt -> 2, 4
4 cap 3 -> 5
5 cap 4 -> 6
- 6 rune "b" -> 7
+ 6 rune1 "b" -> 7
7 alt -> 6, 8
8 cap 5 -> 9
9 match
`},
{"a+|b+", ` 0 fail
- 1 rune "a" -> 2
+ 1 rune1 "a" -> 2
2 alt -> 1, 6
- 3 rune "b" -> 4
+ 3 rune1 "b" -> 4
4 alt -> 3, 6
5* alt -> 1, 3
6 match
`},
{"A[Aa]", ` 0 fail
- 1* rune "A" -> 2
+ 1* rune1 "A" -> 2
2 rune "A"/i -> 3
3 match
`},
{"(?:(?:^).)", ` 0 fail
1* empty 4 -> 2
- 2 rune "\x00\t\v\U0010ffff" -> 3
+ 2 anynotnl -> 3
3 match
`},
}