mirror of
https://github.com/golang/go
synced 2024-11-25 01:57:56 -07:00
fmt: make recursive scan more efficient.
Detect when scan is being called recursively and re-use the same scan state. On my machine, for a recursion-heavy benchmark, this results in 44x speed up. This does impose a 4% penalty on the non-recursive case, which can be removed by heap-allocating the saved state, at 40% performance penalty on the recursive case. Either way is fine with me. R=r CC=golang-dev https://golang.org/cl/4253049
This commit is contained in:
parent
e46acb091f
commit
5bd284e868
@ -103,18 +103,18 @@ func Sscanf(str string, format string, a ...interface{}) (n int, err os.Error) {
|
|||||||
// returns the number of items successfully scanned. If that is less
|
// returns the number of items successfully scanned. If that is less
|
||||||
// than the number of arguments, err will report why.
|
// than the number of arguments, err will report why.
|
||||||
func Fscan(r io.Reader, a ...interface{}) (n int, err os.Error) {
|
func Fscan(r io.Reader, a ...interface{}) (n int, err os.Error) {
|
||||||
s := newScanState(r, true, false)
|
s, old := newScanState(r, true, false)
|
||||||
n, err = s.doScan(a)
|
n, err = s.doScan(a)
|
||||||
s.free()
|
s.free(old)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fscanln is similar to Fscan, but stops scanning at a newline and
|
// Fscanln is similar to Fscan, but stops scanning at a newline and
|
||||||
// after the final item there must be a newline or EOF.
|
// after the final item there must be a newline or EOF.
|
||||||
func Fscanln(r io.Reader, a ...interface{}) (n int, err os.Error) {
|
func Fscanln(r io.Reader, a ...interface{}) (n int, err os.Error) {
|
||||||
s := newScanState(r, false, true)
|
s, old := newScanState(r, false, true)
|
||||||
n, err = s.doScan(a)
|
n, err = s.doScan(a)
|
||||||
s.free()
|
s.free(old)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,9 +122,9 @@ func Fscanln(r io.Reader, a ...interface{}) (n int, err os.Error) {
|
|||||||
// values into successive arguments as determined by the format. It
|
// values into successive arguments as determined by the format. It
|
||||||
// returns the number of items successfully parsed.
|
// returns the number of items successfully parsed.
|
||||||
func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err os.Error) {
|
func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err os.Error) {
|
||||||
s := newScanState(r, false, false)
|
s, old := newScanState(r, false, false)
|
||||||
n, err = s.doScanf(format, a)
|
n, err = s.doScanf(format, a)
|
||||||
s.free()
|
s.free(old)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,15 +138,24 @@ const EOF = -1
|
|||||||
|
|
||||||
// ss is the internal implementation of ScanState.
|
// ss is the internal implementation of ScanState.
|
||||||
type ss struct {
|
type ss struct {
|
||||||
rr io.RuneReader // where to read input
|
rr io.RuneReader // where to read input
|
||||||
buf bytes.Buffer // token accumulator
|
buf bytes.Buffer // token accumulator
|
||||||
nlIsSpace bool // whether newline counts as white space
|
peekRune int // one-rune lookahead
|
||||||
nlIsEnd bool // whether newline terminates scan
|
prevRune int // last rune returned by ReadRune
|
||||||
peekRune int // one-rune lookahead
|
count int // runes consumed so far.
|
||||||
prevRune int // last rune returned by ReadRune
|
atEOF bool // already read EOF
|
||||||
atEOF bool // already read EOF
|
ssave
|
||||||
maxWid int // max width of field, in runes
|
}
|
||||||
wid int // width consumed so far; used in accept()
|
|
||||||
|
// ssave holds the parts of ss that need to be
|
||||||
|
// saved and restored on recursive scans.
|
||||||
|
type ssave struct {
|
||||||
|
validSave bool // is or was a part of an actual ss.
|
||||||
|
nlIsEnd bool // whether newline terminates scan
|
||||||
|
nlIsSpace bool // whether newline counts as white space
|
||||||
|
fieldLimit int // max value of ss.count for this field; fieldLimit <= limit
|
||||||
|
limit int // max value of ss.count.
|
||||||
|
maxWid int // width of this field.
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Read method is only in ScanState so that ScanState
|
// The Read method is only in ScanState so that ScanState
|
||||||
@ -158,21 +167,21 @@ func (s *ss) Read(buf []byte) (n int, err os.Error) {
|
|||||||
|
|
||||||
func (s *ss) ReadRune() (rune int, size int, err os.Error) {
|
func (s *ss) ReadRune() (rune int, size int, err os.Error) {
|
||||||
if s.peekRune >= 0 {
|
if s.peekRune >= 0 {
|
||||||
s.wid++
|
s.count++
|
||||||
rune = s.peekRune
|
rune = s.peekRune
|
||||||
size = utf8.RuneLen(rune)
|
size = utf8.RuneLen(rune)
|
||||||
s.prevRune = rune
|
s.prevRune = rune
|
||||||
s.peekRune = -1
|
s.peekRune = -1
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if s.atEOF || s.nlIsEnd && s.prevRune == '\n' || s.wid >= s.maxWid {
|
if s.atEOF || s.nlIsEnd && s.prevRune == '\n' || s.count >= s.fieldLimit {
|
||||||
err = os.EOF
|
err = os.EOF
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rune, size, err = s.rr.ReadRune()
|
rune, size, err = s.rr.ReadRune()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
s.wid++
|
s.count++
|
||||||
s.prevRune = rune
|
s.prevRune = rune
|
||||||
} else if err == os.EOF {
|
} else if err == os.EOF {
|
||||||
s.atEOF = true
|
s.atEOF = true
|
||||||
@ -217,7 +226,7 @@ func (s *ss) UnreadRune() os.Error {
|
|||||||
} else {
|
} else {
|
||||||
s.peekRune = s.prevRune
|
s.peekRune = s.prevRune
|
||||||
}
|
}
|
||||||
s.wid--
|
s.count--
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,8 +314,19 @@ func (r *readRune) ReadRune() (rune int, size int, err os.Error) {
|
|||||||
var ssFree = newCache(func() interface{} { return new(ss) })
|
var ssFree = newCache(func() interface{} { return new(ss) })
|
||||||
|
|
||||||
// Allocate a new ss struct or grab a cached one.
|
// Allocate a new ss struct or grab a cached one.
|
||||||
func newScanState(r io.Reader, nlIsSpace, nlIsEnd bool) *ss {
|
func newScanState(r io.Reader, nlIsSpace, nlIsEnd bool) (s *ss, old ssave) {
|
||||||
s := ssFree.get().(*ss)
|
// If the reader is a *ss, then we've got a recursive
|
||||||
|
// call to Scan, so re-use the scan state.
|
||||||
|
s, ok := r.(*ss)
|
||||||
|
if ok {
|
||||||
|
old = s.ssave
|
||||||
|
s.limit = s.fieldLimit
|
||||||
|
s.nlIsEnd = nlIsEnd || s.nlIsEnd
|
||||||
|
s.nlIsSpace = nlIsSpace
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s = ssFree.get().(*ss)
|
||||||
if rr, ok := r.(io.RuneReader); ok {
|
if rr, ok := r.(io.RuneReader); ok {
|
||||||
s.rr = rr
|
s.rr = rr
|
||||||
} else {
|
} else {
|
||||||
@ -317,12 +337,20 @@ func newScanState(r io.Reader, nlIsSpace, nlIsEnd bool) *ss {
|
|||||||
s.prevRune = -1
|
s.prevRune = -1
|
||||||
s.peekRune = -1
|
s.peekRune = -1
|
||||||
s.atEOF = false
|
s.atEOF = false
|
||||||
|
s.limit = hugeWid
|
||||||
|
s.fieldLimit = hugeWid
|
||||||
s.maxWid = hugeWid
|
s.maxWid = hugeWid
|
||||||
return s
|
s.validSave = true
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save used ss structs in ssFree; avoid an allocation per invocation.
|
// Save used ss structs in ssFree; avoid an allocation per invocation.
|
||||||
func (s *ss) free() {
|
func (s *ss) free(old ssave) {
|
||||||
|
// If it was used recursively, just restore the old state.
|
||||||
|
if old.validSave {
|
||||||
|
s.ssave = old
|
||||||
|
return
|
||||||
|
}
|
||||||
// Don't hold on to ss structs with large buffers.
|
// Don't hold on to ss structs with large buffers.
|
||||||
if cap(s.buf.Bytes()) > 1024 {
|
if cap(s.buf.Bytes()) > 1024 {
|
||||||
return
|
return
|
||||||
@ -1014,7 +1042,10 @@ func (s *ss) doScanf(format string, a []interface{}) (numProcessed int, err os.E
|
|||||||
if !widPresent {
|
if !widPresent {
|
||||||
s.maxWid = hugeWid
|
s.maxWid = hugeWid
|
||||||
}
|
}
|
||||||
s.wid = 0
|
s.fieldLimit = s.limit
|
||||||
|
if f := s.count + s.maxWid; f < s.fieldLimit {
|
||||||
|
s.fieldLimit = f
|
||||||
|
}
|
||||||
|
|
||||||
c, w := utf8.DecodeRuneInString(format[i:])
|
c, w := utf8.DecodeRuneInString(format[i:])
|
||||||
i += w
|
i += w
|
||||||
@ -1027,7 +1058,7 @@ func (s *ss) doScanf(format string, a []interface{}) (numProcessed int, err os.E
|
|||||||
|
|
||||||
s.scanOne(c, field)
|
s.scanOne(c, field)
|
||||||
numProcessed++
|
numProcessed++
|
||||||
s.maxWid = hugeWid
|
s.fieldLimit = s.limit
|
||||||
}
|
}
|
||||||
if numProcessed < len(a) {
|
if numProcessed < len(a) {
|
||||||
s.errorString("too many operands")
|
s.errorString("too many operands")
|
||||||
|
@ -6,6 +6,7 @@ package fmt_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
. "fmt"
|
. "fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
@ -745,3 +746,119 @@ func TestMultiLine(t *testing.T) {
|
|||||||
t.Errorf("Sscanln: expected io.ErrUnexpectedEOF (ha!); got %s", err)
|
t.Errorf("Sscanln: expected io.ErrUnexpectedEOF (ha!); got %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RecursiveInt accepts an string matching %d.%d.%d....
|
||||||
|
// and parses it into a linked list.
|
||||||
|
// It allows us to benchmark recursive descent style scanners.
|
||||||
|
type RecursiveInt struct {
|
||||||
|
i int
|
||||||
|
next *RecursiveInt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RecursiveInt) Scan(state ScanState, verb int) (err os.Error) {
|
||||||
|
_, err = Fscan(state, &r.i)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next := new(RecursiveInt)
|
||||||
|
_, err = Fscanf(state, ".%v", next)
|
||||||
|
if err != nil {
|
||||||
|
if err == os.ErrorString("input does not match format") || err == io.ErrUnexpectedEOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.next = next
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the same scanning task as RecursiveInt.Scan
|
||||||
|
// but without recurring through scanner, so we can compare
|
||||||
|
// performance more directly.
|
||||||
|
func scanInts(r *RecursiveInt, b *bytes.Buffer) (err os.Error) {
|
||||||
|
r.next = nil
|
||||||
|
_, err = Fscan(b, &r.i)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var c int
|
||||||
|
c, _, err = b.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
if err == os.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c != '.' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next := new(RecursiveInt)
|
||||||
|
err = scanInts(next, b)
|
||||||
|
if err == nil {
|
||||||
|
r.next = next
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeInts(n int) []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
Fprintf(&buf, "1")
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
Fprintf(&buf, ".%d", i+1)
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScanInts(t *testing.T) {
|
||||||
|
testScanInts(t, scanInts)
|
||||||
|
testScanInts(t, func(r *RecursiveInt, b *bytes.Buffer) (err os.Error) {
|
||||||
|
_, err = Fscan(b, r)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const intCount = 1000
|
||||||
|
|
||||||
|
func testScanInts(t *testing.T, scan func(*RecursiveInt, *bytes.Buffer) os.Error) {
|
||||||
|
r := new(RecursiveInt)
|
||||||
|
ints := makeInts(intCount)
|
||||||
|
buf := bytes.NewBuffer(ints)
|
||||||
|
err := scan(r, buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("unexpected error", err)
|
||||||
|
}
|
||||||
|
i := 1
|
||||||
|
for ; r != nil; r = r.next {
|
||||||
|
if r.i != i {
|
||||||
|
t.Fatal("bad scan: expected %d got %d", i, r.i)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i-1 != intCount {
|
||||||
|
t.Fatal("bad scan count: expected %d got %d", intCount, i-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkScanInts(b *testing.B) {
|
||||||
|
b.ResetTimer()
|
||||||
|
ints := makeInts(intCount)
|
||||||
|
var r RecursiveInt
|
||||||
|
for i := b.N - 1; i >= 0; i-- {
|
||||||
|
buf := bytes.NewBuffer(ints)
|
||||||
|
b.StartTimer()
|
||||||
|
scanInts(&r, buf)
|
||||||
|
b.StopTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkScanRecursiveInt(b *testing.B) {
|
||||||
|
b.ResetTimer()
|
||||||
|
ints := makeInts(intCount)
|
||||||
|
var r RecursiveInt
|
||||||
|
for i := b.N - 1; i >= 0; i-- {
|
||||||
|
buf := bytes.NewBuffer(ints)
|
||||||
|
b.StartTimer()
|
||||||
|
Fscan(buf, &r)
|
||||||
|
b.StopTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user