[
https://issues.apache.org/jira/browse/BEAM-3612?focusedWorklogId=161674&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-161674
]
ASF GitHub Bot logged work on BEAM-3612:
----------------------------------------
Author: ASF GitHub Bot
Created on: 01/Nov/18 18:17
Start Date: 01/Nov/18 18:17
Worklog Time Spent: 10m
Work Description: aaltay 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_r230145067
##########
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 {
Review comment:
For my learning:
Would benchmarking output include a benchmark result for the
BenchmarkMethodCalls too in this case? (Or how does b.Run allows it to
distinguish that this is a wrapper not a direct benchmark.)
----------------------------------------------------------------
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:
[email protected]
Issue Time Tracking
-------------------
Worklog Id: (was: 161674)
Time Spent: 2h 40m (was: 2.5h)
> 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 40m
> Remaining Estimate: 0h
>
--
This message was sent by Atlassian JIRA
(v7.6.3#76005)