maintner: replace author/committer regexp with manual code
regexp was 25% of the CPU. Now it's lost in the long tail.
Change-Id: Ifc522d0690dff2e2524ec12354d3790946e5a54a
Reviewed-on: https://go-review.googlesource.com/38725
Reviewed-by: Kevin Burke <kev@inburke.com>
diff --git a/maintner/git.go b/maintner/git.go
index b37c929..8063af4 100644
--- a/maintner/git.go
+++ b/maintner/git.go
@@ -13,7 +13,6 @@
"fmt"
"log"
"os/exec"
- "regexp"
"sort"
"strconv"
"strings"
@@ -368,28 +367,36 @@
return nil
}
-var personRx = regexp.MustCompile(`^(.+) (\d+) ([\+\-]\d\d\d\d)\s*$`)
-
-//
// parsePerson parses an "author" or "committer" value from "git cat-file -p COMMIT"
// The values are like:
// Foo Bar <foobar@gmail.com> 1488624439 +0900
// c.mu must be held for writing.
func (c *Corpus) parsePerson(v []byte) (*gitPerson, time.Time, error) {
- m := personRx.FindSubmatch(v) // TODO(bradfitz): for speed, don't use regexp :(
- if m == nil {
+ v = bytes.TrimSpace(v)
+
+ lastSpace := bytes.LastIndexByte(v, ' ')
+ if lastSpace < 0 {
return nil, time.Time{}, errors.New("failed to match person")
}
+ tz := v[lastSpace+1:] // "+0800"
+ v = v[:lastSpace] // now v is "Foo Bar <foobar@gmail.com> 1488624439"
- ut, err := strconv.ParseInt(string(m[2]), 10, 64)
+ lastSpace = bytes.LastIndexByte(v, ' ')
+ if lastSpace < 0 {
+ return nil, time.Time{}, errors.New("failed to match person")
+ }
+ unixTime := v[lastSpace+1:]
+ nameEmail := v[:lastSpace] // now v is "Foo Bar <foobar@gmail.com>"
+
+ ut, err := strconv.ParseInt(string(unixTime), 10, 64)
if err != nil {
return nil, time.Time{}, err
}
- t := time.Unix(ut, 0).In(c.gitLocation(string(m[3])))
+ t := time.Unix(ut, 0).In(c.gitLocation(tz))
- p, ok := c.gitPeople[string(m[1])]
+ p, ok := c.gitPeople[string(nameEmail)]
if !ok {
- p = &gitPerson{str: string(m[1])}
+ p = &gitPerson{str: string(nameEmail)}
if c.gitPeople == nil {
c.gitPeople = map[string]*gitPerson{}
}
@@ -401,21 +408,22 @@
// v is like '[+-]hhmm'
// c.mu must be held for writing.
-func (c *Corpus) gitLocation(v string) *time.Location {
- if loc, ok := c.zoneCache[v]; ok {
+func (c *Corpus) gitLocation(v []byte) *time.Location {
+ if loc, ok := c.zoneCache[string(v)]; ok {
return loc
}
- h, _ := strconv.Atoi(v[1:3])
- m, _ := strconv.Atoi(v[3:5])
+ s := string(v)
+ h, _ := strconv.Atoi(s[1:3])
+ m, _ := strconv.Atoi(s[3:5])
east := 1
if v[0] == '-' {
east = -1
}
- loc := time.FixedZone(v, east*(h*3600+m*60))
+ loc := time.FixedZone(s, east*(h*3600+m*60))
if c.zoneCache == nil {
c.zoneCache = map[string]*time.Location{}
}
- c.zoneCache[v] = loc
+ c.zoneCache[s] = loc
return loc
}
diff --git a/maintner/git_test.go b/maintner/git_test.go
new file mode 100644
index 0000000..4a40af9
--- /dev/null
+++ b/maintner/git_test.go
@@ -0,0 +1,58 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package maintner
+
+import (
+ "reflect"
+ "testing"
+ "time"
+)
+
+func TestParsePerson(t *testing.T) {
+ var c Corpus
+
+ p, ct, err := c.parsePerson([]byte(" Foo Bar <foo@bar.com> 1257894000 -0800"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ wantp := &gitPerson{str: "Foo Bar <foo@bar.com>"}
+ if !reflect.DeepEqual(p, wantp) {
+ t.Errorf("person = %+v; want %+v", p, wantp)
+ }
+ wantct := time.Unix(1257894000, 0)
+ if !ct.Equal(wantct) {
+ t.Errorf("commit time = %v; want %v", ct, wantct)
+ }
+ zoneName, off := ct.Zone()
+ if want := "-0800"; zoneName != want {
+ t.Errorf("zone name = %q; want %q", zoneName, want)
+ }
+ if want := -28800; off != want {
+ t.Errorf("offset = %v; want %v", off, want)
+ }
+
+ p2, ct2, err := c.parsePerson([]byte("Foo Bar <foo@bar.com> 1257894001 -0800"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if p != p2 {
+ t.Errorf("gitPerson pointer values differ; not sharing memory")
+ }
+ if !ct2.Equal(ct.Add(time.Second)) {
+ t.Errorf("wrong time")
+ }
+}
+
+func BenchmarkParsePerson(b *testing.B) {
+ b.ReportAllocs()
+ in := []byte(" Foo Bar <foo@bar.com> 1257894000 -0800")
+ var c Corpus
+ for i := 0; i < b.N; i++ {
+ _, _, err := c.parsePerson(in)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}