Skip to content

Commit

Permalink
Merge pull request #7 from utahta/improve-performance
Browse files Browse the repository at this point in the history
Improve performance
  • Loading branch information
utahta authored May 11, 2019
2 parents 1bd0ac7 + ae5cfbd commit 3c97c54
Show file tree
Hide file tree
Showing 8 changed files with 276 additions and 57 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
```
178 changes: 174 additions & 4 deletions benchmark_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,46 @@
package validator

import "testing"
import (
"testing"
)

type (
I interface {
Foo() string
}

Impl struct {
F string `valid:"len(3)"`
}

SubTest struct {
Test string `valid:"required"`
}

TestString struct {
BlankTag string `valid:""`
Required string `valid:"required"`
Len string `valid:"len(10)"`
Min string `valid:"min(1)"`
Max string `valid:"max(10)"`
MinMax string `valid:"min(1),max(10)"`
Lt string `valid:"max(9)"`
Lte string `valid:"max(10)"`
Gt string `valid:"min(11)"`
Gte string `valid:"min(10)"`
OmitEmpty string `valid:"optional,min(1),max(10)"`
Sub *SubTest
SubIgnore *SubTest `valid:"-"`
Anonymous struct {
A string `valid:"required"`
}
Iface I
}
)

func (i *Impl) Foo() string {
return i.F
}

func BenchmarkValidateVarSuccess(b *testing.B) {
v := New()
Expand All @@ -9,10 +49,27 @@ 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)
}
}
}

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

s := "1"

b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if err := v.ValidateVar(&s, "len(1)"); err != nil {
b.Fatal(err)
}
}
})
}

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

Expand All @@ -21,10 +78,123 @@ func BenchmarkValidateStructSuccess(b *testing.B) {
IntValue int `valid:"len(5|10)"`
}

validFoo := &Foo{StringValue: "Foobar", IntValue: 7}
s := &Foo{StringValue: "Foobar", IntValue: 7}

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

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

type Foo struct {
StringValue string `valid:"len(5|10)"`
IntValue int `valid:"len(5|10)"`
}

s := &Foo{StringValue: "Foobar", IntValue: 7}

b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if err := v.ValidateStruct(s); err != nil {
b.Fatal(err)
}
}
})
}

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

s := &TestString{
Required: "12345",
Len: "1234567890",
Min: "1",
Max: "1234567890",
MinMax: "12345",
Lt: "123456789",
Lte: "1234567890",
Gt: "12345678901",
Gte: "1234567890",
OmitEmpty: "",
Sub: &SubTest{
Test: "1",
},
SubIgnore: &SubTest{
Test: "",
},
Anonymous: struct {
A string `valid:"required"`
}{
A: "1",
},
Iface: &Impl{
F: "123",
},
}

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

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

s := &TestString{
Required: "12345",
Len: "1234567890",
Min: "1",
Max: "1234567890",
MinMax: "12345",
Lt: "123456789",
Lte: "1234567890",
Gt: "12345678901",
Gte: "1234567890",
OmitEmpty: "",
Sub: &SubTest{
Test: "1",
},
SubIgnore: &SubTest{
Test: "",
},
Anonymous: struct {
A string `valid:"required"`
}{
A: "1",
},
Iface: &Impl{
F: "123",
},
}

b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
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++ {
v.ValidateStruct(validFoo)
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
Loading

0 comments on commit 3c97c54

Please sign in to comment.