Skip to content

Commit

Permalink
reflect/protoreflect: optimize Name.IsValid and FullName.IsValid
Browse files Browse the repository at this point in the history
For simplicity, IsValid was implemented in terms of regular expressions,
which are useful for verifying a string according to a strict grammar,
but performs poorly. Implement the check in terms of hand-written code,
which provides a 20x improvement to validate the name "google.protobuf.Any".

name               old time/op  new time/op  delta
FullNameIsValid-8   683ns ± 2%    35ns ± 1%  -94.86%  (p=0.000 n=10+10)

Change-Id: I980403befca0b72cea22acd274064a46cb02644b
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/238002
Reviewed-by: Damien Neil <dneil@google.com>
  • Loading branch information
dsnet committed Jun 16, 2020
1 parent 0084168 commit 44e4150
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 12 deletions.
50 changes: 38 additions & 12 deletions reflect/protoreflect/proto.go
Expand Up @@ -128,7 +128,6 @@ package protoreflect

import (
"fmt"
"regexp"
"strings"

"google.golang.org/protobuf/encoding/protowire"
Expand Down Expand Up @@ -408,19 +407,14 @@ type EnumRanges interface {
doNotImplement
}

var (
regexName = regexp.MustCompile(`^[_a-zA-Z][_a-zA-Z0-9]*$`)
regexFullName = regexp.MustCompile(`^[_a-zA-Z][_a-zA-Z0-9]*(\.[_a-zA-Z][_a-zA-Z0-9]*)*$`)
)

// Name is the short name for a proto declaration. This is not the name
// as used in Go source code, which might not be identical to the proto name.
type Name string // e.g., "Kind"

// IsValid reports whether n is a syntactically valid name.
// IsValid reports whether s is a syntactically valid name.
// An empty name is invalid.
func (n Name) IsValid() bool {
return regexName.MatchString(string(n))
func (s Name) IsValid() bool {
return consumeIdent(string(s)) == len(s)
}

// Names represent a list of names.
Expand All @@ -443,10 +437,42 @@ type Names interface {
// This should not have any leading or trailing dots.
type FullName string // e.g., "google.protobuf.Field.Kind"

// IsValid reports whether n is a syntactically valid full name.
// IsValid reports whether s is a syntactically valid full name.
// An empty full name is invalid.
func (n FullName) IsValid() bool {
return regexFullName.MatchString(string(n))
func (s FullName) IsValid() bool {
i := consumeIdent(string(s))
if i < 0 {
return false
}
for len(s) > i {
if s[i] != '.' {
return false
}
i++
n := consumeIdent(string(s[i:]))
if n < 0 {
return false
}
i += n
}
return true
}

func consumeIdent(s string) (i int) {
if len(s) == 0 || !isLetter(s[i]) {
return -1
}
i++
for len(s) > i && isLetterDigit(s[i]) {
i++
}
return i
}
func isLetter(c byte) bool {
return c == '_' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')
}
func isLetterDigit(c byte) bool {
return isLetter(c) || ('0' <= c && c <= '9')
}

// Name returns the short name, which is the last identifier segment.
Expand Down
8 changes: 8 additions & 0 deletions reflect/protoreflect/proto_test.go
Expand Up @@ -72,3 +72,11 @@ func TestNameAppend(t *testing.T) {
}
}
}

var sink bool

func BenchmarkFullNameIsValid(b *testing.B) {
for i := 0; i < b.N; i++ {
sink = FullName("google.protobuf.Any").IsValid()
}
}

0 comments on commit 44e4150

Please sign in to comment.