Golang pprof singleton performance

கார்த்திக்
3 min readDec 10, 2021

I always wanted to check and measure performance of simple things to learn about the concept in depth. Is impossible due to time constraint and work load mostly.Recently had some time on my own so thought why not do a refresher on golang pprof. Current golang applications are distributed microservices, whose performance and run times are measured using specialised tool which is not scope of this article. The article focus on using inbuilt pprof to measure the performance of a simple constructor function which creates a new instance of struct and validate the fields inside struct based on the validator rules defined for fields. Below content is based on simple experiment to create an instance of struct using constructor and validate its fields using 3 different approaches.

  1. Create a new instance of validator for each call towards the constructor.
  2. Pass in an instance of validator as one of the input parameter for constructor.
  3. Use global singleton instance of validator across the app.

With out further due here are the findings.

simple user struct used for the experiment

type User struct {
Fname, Lname, Addr1, City, Postcode string`validate:"required"`
Addr2 string
}

New constructor is being used to create a new instance of user instead of declaring it directly.

For simplicity sake the postcode is defined as string and having required rule i.e- for demo purposes we are expecting a string in postcode field if not present we raise validation error using validator. The same applies to name validation as well, expecting string of any length.

Approach -1

First approach being, to create new instance of validator inside constructor as shown below , so for each call to the constructor new instance of validator being initialised and used.

func NewUser(fname, lname, addr1, city, pcode string) (*User, error) {
v := validator.New()
i := &User{
Fname: fname,
Lname: lname,
Addr1: addr1,
City: city,
PostCode: pcode,
}
if err := v.Struct(i); err != nil {
return nil, err
}
}

A simple bench mark function is used to capture time taken to intialise new user using constructor.

func benchmarkNewUser(b *testing.B) {
for i := 0; i < b.N; i++ {
_,_= NewUser(b.Name(), b.Name(), b.Name(),b.Name(),b.Name(),b.Name(), b.Name())
}
}
func BenchmarkNewUser(b *testing.B) {
benchmarkNewUser(b)
}

count : 5 is the number of times is benchmark ran.

goos: linux
goarch: amd64
cpu: Intel(R) Core(TM) i7–10510U CPU @ 1.80GHz
BenchmarkNewUser-8 46929 22373 ns/op
BenchmarkNewUser-8 52742 21238 ns/op
BenchmarkNewUser-8 53509 21459 ns/op
BenchmarkNewUser-8 49224 22353 ns/op
BenchmarkNewUser-8 54099 22435 ns/op
PASS
ok 7.099s

Approach -2

Second approach is, to pass in validator instance as one of the input parameter to constructor.


func NewUser(parm1,parm2,parm3 string,v *validator.Validate) (*instance,error){
i:=&instance{
parm1,parm2,param3
}
if err:=v.Struct(i);err!=nil{
return nil,err
}
}

Both count number and the benchmark remains the same as first approach.

goos: linux
goarch: amd64
cpu: Intel(R) Core(TM) i7–10510U CPU @ 1.80GHz
BenchmarkNewUser-8 46929 22373 ns/op
BenchmarkNewUser-8 52742 21238 ns/op
BenchmarkNewUser-8 53509 21459 ns/op
BenchmarkNewUser-8 49224 22353 ns/op
BenchmarkNewUser-8 54099 22435 ns/op
PASS
ok 7.099s

Negligible amount of improvement in ops.

Approach -3

Final approach the wining one is to create a global instance of validator and use it in the package.

import{
“github.com/ehrktia/bank/app/utils/validate”
}
//todo move this to main func
func init(){
validate.NewValidator()
}
func NewUser(parm1,parm2,parm3 string)(*instance,error){
i:=&instance{
parm1,parm2,parm3
}
if err:=validate.Validate.Struct(i);err!=nil{
return nil,err
}
}

here we initialise a validator in main function by calling the NewValidator()
which in turn creates new instance of validator ready to be used in application.

validate.go

var Validate *Validate.Validator
var once &sync.Once{}
func NewValidator(){
once.Do(func(){
Validate=validator.New()
}
)
}

Inside validate pkg, a global variable Validate is declared and this is initialised by NewValidator() .To ensure we initialize only one instance of the validator globally it is called in under sync once do function.


goos: linux
goarch: amd64
cpu: Intel(R) Core(TM) i7–10510U CPU @ 1.80GHz
BenchmarkNewUser-8 2005014 636.3 ns/op
BenchmarkNewUser-8 1879420 579.7 ns/op
BenchmarkNewUser-8 1891534 594.4 ns/op
BenchmarkNewUser-8 1735612 590.9 ns/op
BenchmarkNewUser-8 1888153 681.4 ns/op
PASS
ok 9.255s

considerable improvement in op/s reduced from 23kns/op to 600ns/op.

Conclusion :

if any third party package used inside app offers concurrent safe instance better to create global instance and use it across app.

Foot Note: please correct me if am wrong , provide me with your valuable feedback which would help me grow in all aspects.

--

--

கார்த்திக்

Simple guy with free associative mind.அதிகம் சொல்ல ஒன்னும் இல்ல....