[ 
https://issues.apache.org/jira/browse/BEAM-3612?focusedWorklogId=161700&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-161700
 ]

ASF GitHub Bot logged work on BEAM-3612:
----------------------------------------

                Author: ASF GitHub Bot
            Created on: 01/Nov/18 19:01
            Start Date: 01/Nov/18 19:01
    Worklog Time Spent: 10m 
      Work Description: lostluck commented on a change in pull request #6893: 
[BEAM-3612] Add a benchmark for method invocation methods.
URL: https://github.com/apache/beam/pull/6893#discussion_r230159954
 
 

 ##########
 File path: sdks/go/pkg/beam/core/runtime/exec/fn_test.go
 ##########
 @@ -400,3 +400,210 @@ func BenchmarkInvokeFnCallExtra(b *testing.B) {
        }
        b.Log(n)
 }
+
+// Foo is a struct with a method for measuring method invocation
+// overhead for StructuralDoFns.
+type Foo struct {
+       A int
+}
+
+// WhatsA is a method for measuring a baseline of structural dofn overhead.
+func (f *Foo) WhatsA(b int) int {
+       return f.A + b
+}
+
+// WhatsB is a comparable direct function for baseline comparison.
+func WhatsB(b int) int {
+       return 32 + b
+}
+
+// Expclicit Receiver Type assertion shim.
+type callerFooRInt struct {
+       fn func(*Foo, int) int
+}
+
+func funcMakerFooRInt(fn interface{}) reflectx.Func {
+       f := fn.(func(*Foo, int) int)
+       return &callerFooRInt{fn: f}
+}
+
+func (c *callerFooRInt) Name() string {
+       return reflectx.FunctionName(c.fn)
+}
+
+func (c *callerFooRInt) Type() reflect.Type {
+       return reflect.TypeOf(c.fn)
+}
+
+func (c *callerFooRInt) Call(args []interface{}) []interface{} {
+       a := c.fn(args[0].(*Foo), args[1].(int))
+       return []interface{}{a}
+}
+
+// To satisfy reflectx.Func2x1
+func (c *callerFooRInt) Call2x1(a1, a2 interface{}) interface{} {
+       return c.fn(a1.(*Foo), a2.(int))
+}
+
+// Implicit Receiver type assertion shim.
+type callerInt struct {
+       fn func(int) int
+}
+
+func funcMakerInt(fn interface{}) reflectx.Func {
+       f := fn.(func(int) int)
+       return &callerInt{fn: f}
+}
+
+func (c *callerInt) Name() string {
+       return reflectx.FunctionName(c.fn)
+}
+
+func (c *callerInt) Type() reflect.Type {
+       return reflect.TypeOf(c.fn)
+}
+
+func (c *callerInt) Call(args []interface{}) []interface{} {
+       a := c.fn(args[0].(int))
+       return []interface{}{a}
+}
+
+func (c *callerInt) Call1x1(a0 interface{}) interface{} {
+       return c.fn(a0.(int))
+}
+
+// BenchmarkMethodCalls measures the overhead of different ways of
+// "generically" invoking methods.
+//
+// This benchmark invokes methods along several different axes
+// * Implicit or Explicit method Receiver
+// * Pre-wrapped values and pre-allocated slices.
+// * Invocations along the following ways
+//   * Indirect via extracting from a reflect.Value.Interface()
+//   * Reflect Package (reflect.Value.Call())
+//   * Beam's reflectx.Func, and reflectx.FuncNxM interfaces
+//       * Beam's default reflection based reflectx.Func shim
+//       * A Type assertion specialized reflectx.Func shim
+//
+// The Implicit or Explicit method receiver difference exists because
+// Go's reflect package treats the two cases different, and there are
+// performance implications this benchmark captures. Implicit adds a
+// fixed amount of overhead perf invocation giving a performance penalty
+// to Structural DoFns.
+//
+// PreAlocating slices and values serves as a comparison point for how much
+// overhead not doing these things costs. In particular in wrapping/unwrapping
+// values with reflect.ValueOf and extracting them with reflect.Value's
+// Interface() method.
+func BenchmarkMethodCalls(b *testing.B) {
+       f := &Foo{A: 3}
+       var gi interface{}
+       g := &Foo{A: 42}
+       gi = g
+       gV := reflect.ValueOf(g)
+       fV := reflect.ValueOf(f)
+
+       indirectFunc := reflect.ValueOf(WhatsB).Interface().(func(int) int)
+
+       nrF := fV.Method(0)
+       nrFi := nrF.Interface().(func(int) int)
+       rxnrF := reflectx.MakeFunc(nrFi)
+       rx0x1nrF := reflectx.ToFunc1x1(rxnrF)
+       shimnrF := funcMakerInt(nrFi)             // as if this shim were 
registered
+       shim0x1nrF := reflectx.ToFunc1x1(shimnrF) // would be MakeFunc0x1 if 
registered
+
+       wrF := fV.Type().Method(0).Func
+       wrFi := wrF.Interface().(func(*Foo, int) int)
+
+       rxF := reflectx.MakeFunc(wrFi)
+       rx1x1F := reflectx.ToFunc2x1(rxF)
+       shimF := funcMakerFooRInt(wrFi)       // as if this shim were registered
+       shim1x1F := reflectx.ToFunc2x1(shimF) // would be MakeFunc1x1 if 
registered
+
+       var a int
+       var ai interface{} = a
+       aV := reflect.ValueOf(a)
+       rvSlice := []reflect.Value{aV}
+       grvSlice := []reflect.Value{gV, aV}
+       efaceSlice := []interface{}{a}
+       gEfaceSlice := []interface{}{g, a}
+
+       tests := []struct {
+               name string
+               fn   func()
+       }{
+               {"DirectMethod", func() { a = g.WhatsA(a) }}, // Baseline as 
low as we can go.
+               {"DirectFunc", func() { a = WhatsB(a) }},     // For comparison 
purposes
+
+               {"IndirectFunc", func() { a = indirectFunc(a) }},         // 
For comparison purposes
+               {"IndirectImplicit", func() { a = nrFi(a) }},             // 
Measures the indirection through reflect.Value cost.
+               {"TypeAssertedImplicit", func() { ai = nrFi(ai.(int)) }}, // 
Measures the type assertion cost over the above.
+
+               {"ReflectCallImplicit", func() { a = 
nrF.Call([]reflect.Value{reflect.ValueOf(a)})[0].Interface().(int) }},
+               {"ReflectCallImplicit-NoWrap", func() { a = 
nrF.Call([]reflect.Value{aV})[0].Interface().(int) }},
+               {"ReflectCallImplicit-NoReallocSlice", func() { a = 
nrF.Call(rvSlice)[0].Interface().(int) }},
+
+               {"ReflectXCallImplicit", func() { a = 
rxnrF.Call([]interface{}{a})[0].(int) }},
+               {"ReflectXCallImplicit-NoReallocSlice", func() { a = 
rxnrF.Call(efaceSlice)[0].(int) }},
+               {"ReflectXCall1x1Implicit", func() { a = 
rx0x1nrF.Call1x1(a).(int) }}, // Measures the default shimfunc overhead.
+
+               {"ShimedCallImplicit", func() { a = 
shimnrF.Call([]interface{}{a})[0].(int) }},          // What we're currently 
using for invoking methods
+               {"ShimedCallImplicit-NoReallocSlice", func() { a = 
shimnrF.Call(efaceSlice)[0].(int) }}, // Closer to what we're using now.
+               {"ShimedCall1x1Implicit", func() { a = 
shim0x1nrF.Call1x1(a).(int) }},
+
+               {"IndirectExplicit", func() { a = wrFi(g, a) }},                
     // Measures the indirection through reflect.Value cost.
+               {"TypeAssertedExplicit", func() { ai = wrFi(gi.(*Foo), 
ai.(int)) }}, // Measures the type assertion cost over the above.
+
+               {"ReflectCallExplicit", func() { a = 
wrF.Call([]reflect.Value{reflect.ValueOf(g), 
reflect.ValueOf(a)})[0].Interface().(int) }},
+               {"ReflectCallExplicit-NoWrap", func() { a = 
wrF.Call([]reflect.Value{gV, aV})[0].Interface().(int) }},
+               {"ReflectCallExplicit-NoReallocSlice", func() { a = 
wrF.Call(grvSlice)[0].Interface().(int) }},
+
+               {"ReflectXCallExplicit", func() { a = rxF.Call([]interface{}{g, 
a})[0].(int) }},
+               {"ReflectXCallExplicit-NoReallocSlice", func() { a = 
rxF.Call(gEfaceSlice)[0].(int) }},
+               {"ReflectXCall2x1Explicit", func() { a = rx1x1F.Call2x1(g, 
a).(int) }},
+
+               {"ShimedCallExplicit", func() { a = shimF.Call([]interface{}{g, 
a})[0].(int) }},
+               {"ShimedCallExplicit-NoReallocSlice", func() { a = 
shimF.Call(gEfaceSlice)[0].(int) }},
+               {"ShimedCall2x1Explicit", func() { a = shim1x1F.Call2x1(g, 
a).(int) }},
+       }
+       for _, test := range tests {
+               b.Run(test.name, func(b *testing.B) {
+                       for i := 0; i < b.N; i++ {
+                               test.fn()
+                       }
+               })
+       }
+       b.Log(a)
+}
+
+/*
 
 Review comment:
   This keeps a record of *why* a decision was made, and allows us to see what 
changed over time, and to keep us honest if for whatever reason reflection 
becomes sufficiently fast to avoid dealing with this altogether. Putting it in 
the wiki is a good idea, but I'd prefer to do that once I set my sights making 
the Wiki useful for the Go SDK. There's too much pre work I'd need to do before 
I could just move these numbers there.
   
   The precise measurements are going to change, but their relative ordering 
and magnitudes will probably not, short of differences between Go versions. 
This actually happened with Go 1.10 -> Go 1.11 where method invocation with the 
Implicit Receiver improved by ~50ns/op since they started doing better caching 
of the implicit receivers. If a complete re-write to use Explicit Receivers 
takes long enough, it could be that Go removes the distinction performance 
wise, which would let us keep the simpler code.

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


Issue Time Tracking
-------------------

    Worklog Id:     (was: 161700)
    Time Spent: 2h 50m  (was: 2h 40m)

> Make it easy to generate type-specialized Go SDK reflectx.Funcs
> ---------------------------------------------------------------
>
>                 Key: BEAM-3612
>                 URL: https://issues.apache.org/jira/browse/BEAM-3612
>             Project: Beam
>          Issue Type: Improvement
>          Components: sdk-go
>            Reporter: Henning Rohde
>            Assignee: Robert Burke
>            Priority: Major
>          Time Spent: 2h 50m
>  Remaining Estimate: 0h
>




--
This message was sent by Atlassian JIRA
(v7.6.3#76005)

Reply via email to