Skip to content

Commit

Permalink
perf: now 30% faster than before
Browse files Browse the repository at this point in the history
  • Loading branch information
utahta committed May 11, 2019
1 parent a5520fc commit ae5cfbd
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 73 deletions.
11 changes: 11 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ jobs:
docker:
- image: circleci/golang:1.11
working_directory: /go/src/github.com/utahta/go-validator
steps:
- checkout
- run:
name: Run test
command: make test

"go-1.12":
docker:
- image: circleci/golang:1.12
working_directory: /go/src/github.com/utahta/go-validator
steps:
- checkout
- run:
Expand All @@ -29,3 +39,4 @@ workflows:
jobs:
- "go-1.10"
- "go-1.11"
- "go-1.12"
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,19 @@ go-validator is a data validation library for Go.
```sh
go get -u github.com/utahta/go-validator
```

# Benchmarks

3.2 GHz Intel Core i7, 64 GB 2667 MHz DDR4
```go
goos: darwin
goarch: amd64
pkg: github.com/utahta/go-validator
BenchmarkValidateVarSuccess-12 20000000 57.5 ns/op 0 B/op 0 allocs/op
BenchmarkValidateVarParallelSuccess-12 100000000 12.8 ns/op 0 B/op 0 allocs/op
BenchmarkValidateStructSuccess-12 10000000 184 ns/op 0 B/op 0 allocs/op
BenchmarkValidateStructParallelSuccess-12 50000000 34.2 ns/op 0 B/op 0 allocs/op
BenchmarkValidateStructComplexSuccess-12 1000000 1072 ns/op 32 B/op 3 allocs/op
BenchmarkValidateStructComplexParallelSuccess-12 10000000 216 ns/op 32 B/op 3 allocs/op
BenchmarkValidateVarFailure-12 10000000 173 ns/op 208 B/op 2 allocs/op
```
65 changes: 45 additions & 20 deletions benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ func BenchmarkValidateVarSuccess(b *testing.B) {

b.ResetTimer()
for n := 0; n < b.N; n++ {
v.ValidateVar(&s, "len(1)")
if err := v.ValidateVar(&s, "len(1)"); err != nil {
b.Fatal(err)
}
}
}

Expand All @@ -61,7 +63,9 @@ func BenchmarkValidateVarParallelSuccess(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
v.ValidateVar(&s, "len(1)")
if err := v.ValidateVar(&s, "len(1)"); err != nil {
b.Fatal(err)
}
}
})
}
Expand All @@ -78,7 +82,9 @@ func BenchmarkValidateStructSuccess(b *testing.B) {

b.ResetTimer()
for n := 0; n < b.N; n++ {
v.ValidateStruct(s)
if err := v.ValidateStruct(s); err != nil {
b.Fatal(err)
}
}
}

Expand All @@ -95,7 +101,9 @@ func BenchmarkValidateStructParallelSuccess(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
v.ValidateStruct(s)
if err := v.ValidateStruct(s); err != nil {
b.Fatal(err)
}
}
})
}
Expand All @@ -104,15 +112,15 @@ func BenchmarkValidateStructComplexSuccess(b *testing.B) {
v := New()

s := &TestString{
Required: "Required",
Len: "length==10",
Min: "min=1",
Required: "12345",
Len: "1234567890",
Min: "1",
Max: "1234567890",
MinMax: "12345",
Lt: "012345678",
Lte: "0123456789",
Gt: "01234567890",
Gte: "0123456789",
Lt: "123456789",
Lte: "1234567890",
Gt: "12345678901",
Gte: "1234567890",
OmitEmpty: "",
Sub: &SubTest{
Test: "1",
Expand All @@ -132,23 +140,25 @@ func BenchmarkValidateStructComplexSuccess(b *testing.B) {

b.ResetTimer()
for n := 0; n < b.N; n++ {
v.ValidateStruct(s)
if err := v.ValidateStruct(s); err != nil {
b.Fatal(err)
}
}
}

func BenchmarkValidateStructComplexParallelSuccess(b *testing.B) {
v := New()

s := &TestString{
Required: "Required",
Len: "length==10",
Min: "min=1",
Required: "12345",
Len: "1234567890",
Min: "1",
Max: "1234567890",
MinMax: "12345",
Lt: "012345678",
Lte: "0123456789",
Gt: "01234567890",
Gte: "0123456789",
Lt: "123456789",
Lte: "1234567890",
Gt: "12345678901",
Gte: "1234567890",
OmitEmpty: "",
Sub: &SubTest{
Test: "1",
Expand All @@ -169,7 +179,22 @@ func BenchmarkValidateStructComplexParallelSuccess(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
v.ValidateStruct(s)
if err := v.ValidateStruct(s); err != nil {
b.Fatal(err)
}
}
})
}

func BenchmarkValidateVarFailure(b *testing.B) {
v := New()

s := "12"

b.ResetTimer()
for n := 0; n < b.N; n++ {
if err := v.ValidateVar(&s, "len(1)"); err == nil {
b.Fatal("want invalid argument error, but got nil")
}
}
}
2 changes: 2 additions & 0 deletions field_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package validator

type (
fieldCache struct {
index int
isPrivate bool
name string
tagValue string
tagChunk *tagChunk
}
)
18 changes: 9 additions & 9 deletions func.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ var (
defaultFuncMap = FuncMap{
"required": hasValue,
"req": hasValue,
"empty": zeroValue,
"zero": zeroValue,
"empty": isZeroValue,
"zero": isZeroValue,
"alpha": isAlpha,
"alphanum": isAlphaNum,
"alphaunicode": isAlphaUnicode,
Expand Down Expand Up @@ -124,7 +124,7 @@ func hasValue(_ context.Context, f Field, _ FuncOption) (bool, error) {
return !isEmpty(f), nil
}

func zeroValue(_ context.Context, f Field, _ FuncOption) (bool, error) {
func isZeroValue(_ context.Context, f Field, _ FuncOption) (bool, error) {
return isEmpty(f), nil
}

Expand Down Expand Up @@ -293,11 +293,11 @@ func minLength(_ context.Context, f Field, opt FuncOption) (bool, error) {
v := f.current
switch v.Kind() {
case reflect.String, reflect.Array, reflect.Map, reflect.Slice:
min, err := strconv.Atoi(minStr)
min, err := strconv.ParseInt(minStr, 10, 64)
if err != nil {
return false, err
}
return min <= v.Len(), nil
return min <= int64(v.Len()), nil

case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
min, err := strconv.ParseInt(minStr, 10, 64)
Expand Down Expand Up @@ -334,11 +334,11 @@ func maxLength(_ context.Context, f Field, opt FuncOption) (bool, error) {
v := f.current
switch v.Kind() {
case reflect.String, reflect.Array, reflect.Map, reflect.Slice:
max, err := strconv.Atoi(maxStr)
max, err := strconv.ParseInt(maxStr, 10, 64)
if err != nil {
return false, err
}
return v.Len() <= max, nil
return int64(v.Len()) <= max, nil

case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
max, err := strconv.ParseInt(maxStr, 10, 64)
Expand Down Expand Up @@ -373,11 +373,11 @@ func eqLength(_ context.Context, f Field, opt FuncOption) (bool, error) {
v := f.current
switch v.Kind() {
case reflect.String, reflect.Array, reflect.Map, reflect.Slice:
i, err := strconv.Atoi(str)
i, err := strconv.ParseInt(str, 10, 64)
if err != nil {
return false, err
}
return v.Len() == i, nil
return int64(v.Len()) == i, nil

case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i, err := strconv.ParseInt(str, 10, 64)
Expand Down
26 changes: 15 additions & 11 deletions tag_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ import (
"strings"
)

func (v *Validator) tagParse(rawTag string) (*tagChunk, error) {
func (v *Validator) parseTag(rawTag string) (*tagChunk, error) {
if tags, ok := v.tagCache.Load(rawTag); ok {
return tags, nil
}

var (
rootTagChunk tagChunk
chunk *tagChunk
orParsing = false
rootChunk tagChunk
chunk *tagChunk
orParsing = false
)
const optionalTagName = "optional"

chunk = &rootTagChunk
chunk = &rootChunk

s := newTagScanner(rawTag)
loop:
Expand All @@ -37,7 +37,8 @@ loop:
}

if orParsing {
chunk.Tags[len(chunk.Tags)-1].Params = append(chunk.Tags[len(chunk.Tags)-1].Params, lit)
idx := len(chunk.Tags) - 1
chunk.Tags[idx].Params = append(chunk.Tags[idx].Params, lit)
} else {
tag, err := v.newTag(lit)
if err != nil {
Expand All @@ -56,7 +57,8 @@ loop:
}

if orParsing {
chunk.Tags[len(chunk.Tags)-1].Params = append(chunk.Tags[len(chunk.Tags)-1].Params, lit)
idx := len(chunk.Tags) - 1
chunk.Tags[idx].Params = append(chunk.Tags[idx].Params, lit)
} else {
tag, err := v.newTag(lit)
if err != nil {
Expand All @@ -75,7 +77,8 @@ loop:
}

if orParsing {
chunk.Tags[len(chunk.Tags)-1].Params = append(chunk.Tags[len(chunk.Tags)-1].Params, lit)
idx := len(chunk.Tags) - 1
chunk.Tags[idx].Params = append(chunk.Tags[idx].Params, lit)
} else {
chunk.Tags = append(chunk.Tags, Tag{Name: "or", Params: []string{lit}, validateFn: v.FuncMap["or"]})
}
Expand All @@ -84,7 +87,8 @@ loop:
case nextSeparator:
if lit != "" && lit != optionalTagName {
if orParsing {
chunk.Tags[len(chunk.Tags)-1].Params = append(chunk.Tags[len(chunk.Tags)-1].Params, lit)
idx := len(chunk.Tags) - 1
chunk.Tags[idx].Params = append(chunk.Tags[idx].Params, lit)
} else {
tag, err := v.newTag(lit)
if err != nil {
Expand All @@ -99,9 +103,9 @@ loop:
}
}

v.tagCache.Store(rawTag, &rootTagChunk)
v.tagCache.Store(rawTag, &rootChunk)

return &rootTagChunk, nil
return &rootChunk, nil
}

// newTag returns Tag.
Expand Down
6 changes: 3 additions & 3 deletions tag_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ func Test_tagParse(t *testing.T) {

for _, tc := range testcases {
t.Run(tc.rawTag, func(t *testing.T) {
chunk, err := New().tagParse(tc.rawTag)
chunk, err := New().parseTag(tc.rawTag)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -390,7 +390,7 @@ func Test_tagParseInvalid(t *testing.T) {
}

for _, tc := range testcases {
_, err := New().tagParse(tc.rawTag)
_, err := New().parseTag(tc.rawTag)
if err.Error() != tc.wantError {
t.Errorf("want `%v`, got `%v`", tc.wantError, err.Error())
}
Expand All @@ -400,7 +400,7 @@ func Test_tagParseInvalid(t *testing.T) {
func Test_tagCache(t *testing.T) {
const rawTag = "required,min(1),max(10)"
v := New()
want, err := v.tagParse(rawTag)
want, err := v.parseTag(rawTag)
if err != nil {
t.Fatal(err)
}
Expand Down
Loading

0 comments on commit ae5cfbd

Please sign in to comment.