📜 ⬆️ ⬇️

Optional Arguments in Go Functions

Go has no syntax for defining optional arguments in functions, so you have to use workarounds. I know 2:

  1. Pass a structure containing all optional arguments in the fields:

    funcStructOpts(Opts{p1: 1, p2: 2, p8: 8, p9: 9, p10: 10}) 
  2. The method proposed by Rob Pike using functional arguments:
    ')
     funcWithOpts(WithP1(1), WithP2(2), WithP8(8), WithP9(9), WithP10(10)) 

The second method basically does the same thing, but with syntactic sugar. The thought didn’t give me any peace, but how much does this sugar cost?

For tests, I used a structure with 10 options:

 type Opts struct { p1, p2, p3, p4, p5, p6, p7, p8, p9, p10 int } 

and 2 empty functions:

 func funcStructOpts(o Opts) { } 

 func funcWithOpts(opts ...OptsFunc) { o := &Opts{} for _, opt := range opts { opt(o) } } 

For those who have not worked with functional arguments I will tell you a little how they work. Each option is described as a function that returns a function that changes the structure with parameters, for example:

 func WithP1(v int) OptsFunc { return func(opts *Opts) { opts.p1 = v } } 

where OptsFunc is a type OptsFunc func(*Opts)

When a function is called, they are passed as arguments, and inside the function in a loop, they fill in the structure with arguments:

 o := &Opts{} for _, opt := range opts { opt(o) } 

Here the magic ends, now we have a filled structure, it remains only to find out how much sugar costs. For this, I wrote a simple benchmark:

 func BenchmarkStructOpts(b *testing.B) { for i := 0; i < bN; i++ { funcStructOpts(Opts{ p1: i, p2: i + 2, p3: i + 3, p4: i + 4, p5: i + 5, p6: i + 6, p7: i + 7, p8: i + 8, p9: i + 9, p10: i + 10, }) } } func BenchmarkWithOpts(b *testing.B) { for i := 0; i < bN; i++ { funcWithOpts(WithP1(i), WithP2(i+2), WithP3(i+3), WithP4(i+4), WithP5(i+5), WithP6(i+6), WithP7(i+7), WithP8(i+8), WithP9(i+9), WithP10(i+10)) } } 

For testing, I used Go 1.9 on an Intel® Core (TM) i7-4700HQ CPU @ 2.40GHz.

Results:

BenchmarkStructOpts-8 100000000 10.7 ns/op 0 B/op 0 allocs/op
BenchmarkWithOpts-8 3000000 399 ns/op 240 B/op 11 allocs/op

The results are contradictory, on the one hand, the difference is almost 40 times, on the other - it is hundreds of nanoseconds.

It became interesting to me, and what time is spent on, the pprof output is lower:



Everything is logical, time is spent on allocating memory for anonymous functions, and as you know, malloc is time, a lot of time ...

For the purity of the experiment, I checked what happens when you call without arguments:

 func BenchmarkEmptyStructOpts(b *testing.B) { for i := 0; i < bN; i++ { funcStructOpts(Opts{}) } } func BenchmarkEmptyWithOpts(b *testing.B) { for i := 0; i < bN; i++ { funcWithOpts() } } 

Here the difference is slightly less, about 20 times:

BenchmarkEmptyStructOpts-8 1000000000 2.75 ns/op 0 B/op 0 allocs/op
BenchmarkEmptyWithOpts-8 30000000 57.0 ns/op 80 B/op 1 allocs/op

findings


For myself, I never decided what is best. I propose to talk a little in the comments, and to collect statistics a poll is below.

Source: https://habr.com/ru/post/344352/


All Articles