GithubHelp home page GithubHelp logo

Comments (2)

kawasin73 avatar kawasin73 commented on July 23, 2024

Gen0(int) int から Gen19(int) int までの 20 個の関数を呼び出すベンチマークを以下の 5 種類の場合において計測した。

  • BenchmarkStruct:ある struct にインライン化を無効に設定したメソッド Gen0 ~ Gen19 までが定義されていて、その struct を利用する(抽象化はできないが、以後のベンチマークの基準となる)
  • BenchmarkSwitcherif 文を使って条件 t によって振る舞いを変えるインライン化を無効に設定したメソッド Gen0 ~ Gen19 までが定義されている Switcher 構造体を利用する
  • Gen0 ~ Gen19 までが定義されたインターフェイス Interface が宣言されており、そのインターフェイスに適応した struct0 から struct999 までの 1000 種類の構造体が定義されている。その中からランダムに構造体の種類を選択して作成し Interface 経由で各メソッドを呼び出す
  • Gen0 ~ Gen19 までの関数ポインタをフィールドにもつ構造体 FuncPointers が定義されており、そのフィールドに Gen0 ~ Gen19 までの関数を設定して呼び出す。
  • FuncsPointeres と同様に gen0 ~ gen19 までの関数ポインタをフィールドにもつ構造体 FuncPointersInner が定義されており、その関数ポインターを実行する Gen0 ~ Gen19 メソッドを定義してこの公開メソッド経由で呼び出す。この公開メソッドはインライン化を無効にするフラグは付与しない。

結果は以下の通り。ベンチマークでは、1 op あたり 20 回関数呼び出しをしている。

$ go test -bench=.
goos: darwin
goarch: amd64
pkg: github.com/kawasin73/sample-go/interface
BenchmarkStruct-4              	30000000	        49.8 ns/op
BenchmarkSwitcher-4            	20000000	        59.2 ns/op
BenchmarkInterface-4           	10000000	       117 ns/op
BenchmarkFuncPointer-4         	20000000	        56.1 ns/op
BenchmarkFuncPointersInner-4   	10000000	       119 ns/op
PASS
ok  	github.com/kawasin73/sample-go/interface	6.909s

この結果を見ると、メソッドの中で条件分岐をして処理を切り分けるものと、関数ポインターからの呼び出しがほぼ同じくらい速く、通常の関数呼び出しとほぼ同じ速度で実行されていることがわかる。

また、インターフェイス経由の呼び出しとラップした関数経由で関数ポインターを実行するものはほぼ同じくらいの速度で、関数呼び出し 2 回分程度の時間がかかっていることがわかる。
関数をラップしたメソッドの場合は、内部で非公開関数を呼び出しているためインライン化がされず2回の関数呼び出しの時間がかかっているものと考えられる。

関数呼び出しのオーバーヘッドは 2 ~ 3 ns 程度であることがわかる。

また、インターフェイスに合致する構造体の数を変えてインターフェイス呼び出しを計測すると以下のようになり、結果がほぼ変わらないことがわかる。

インターフェイスに合致する構造体の数 結果
1 113 ns/op
1000 117 ns/op

ここで、処理の抽象化を実現する方法として 条件分岐を使う手法関数ポインタを実行する 手法が候補にあがる。

速度で見るとわずかに関数ポインタを実行する手法が速いが、関数ポインタは公開されたフィールドであるためパッケージの外部から差し替えられる可能性がある。非公開フィールドにして公開メソッド経由で呼び出すと 2 回分の関数呼び出しのオーバーヘッドが発生する。

条件分岐を利用する方法では条件分岐命令が遅くなることが懸念されるが、zcbit では設定されたバイトオーダーは変更されないため、分岐予測が有効に働きデメリットは軽減されることが予想される。

よって、条件分岐を利用して処理を切り分ける 手法を採用する。

Source Code

main_test.go

package main

import (
	"testing"
	"math/rand"
)

func BenchmarkStruct(b *testing.B) {
	inter := struct0{n: 1}
	for i := 0; i < b.N; i++ {
		j := i
		j = inter.Gen0(j)
		j = inter.Gen1(j)
		j = inter.Gen2(j)
		j = inter.Gen3(j)
		j = inter.Gen4(j)
		j = inter.Gen5(j)
		j = inter.Gen6(j)
		j = inter.Gen7(j)
		j = inter.Gen8(j)
		j = inter.Gen9(j)
		j = inter.Gen10(j)
		j = inter.Gen11(j)
		j = inter.Gen12(j)
		j = inter.Gen13(j)
		j = inter.Gen14(j)
		j = inter.Gen15(j)
		j = inter.Gen16(j)
		j = inter.Gen17(j)
		j = inter.Gen18(j)
		j = inter.Gen19(j)
	}
}

func BenchmarkSwitcher(b *testing.B) {
	inter := switcher{t: 0, n: 1}
	for i := 0; i < b.N; i++ {
		j := i
		j = inter.Gen0(j)
		j = inter.Gen1(j)
		j = inter.Gen2(j)
		j = inter.Gen3(j)
		j = inter.Gen4(j)
		j = inter.Gen5(j)
		j = inter.Gen6(j)
		j = inter.Gen7(j)
		j = inter.Gen8(j)
		j = inter.Gen9(j)
		j = inter.Gen10(j)
		j = inter.Gen11(j)
		j = inter.Gen12(j)
		j = inter.Gen13(j)
		j = inter.Gen14(j)
		j = inter.Gen15(j)
		j = inter.Gen16(j)
		j = inter.Gen17(j)
		j = inter.Gen18(j)
		j = inter.Gen19(j)
	}
}

func BenchmarkInterface(b *testing.B) {
	var inter Interface = newStruct(rand.Intn(10))
	for i := 0; i < b.N; i++ {
		j := i
		j = inter.Gen0(j)
		j = inter.Gen1(j)
		j = inter.Gen2(j)
		j = inter.Gen3(j)
		j = inter.Gen4(j)
		j = inter.Gen5(j)
		j = inter.Gen6(j)
		j = inter.Gen7(j)
		j = inter.Gen8(j)
		j = inter.Gen9(j)
		j = inter.Gen10(j)
		j = inter.Gen11(j)
		j = inter.Gen12(j)
		j = inter.Gen13(j)
		j = inter.Gen14(j)
		j = inter.Gen15(j)
		j = inter.Gen16(j)
		j = inter.Gen17(j)
		j = inter.Gen18(j)
		j = inter.Gen19(j)
	}
}

func BenchmarkFuncPointer(b *testing.B) {
	p := newFuncs()
	for i := 0; i < b.N; i++ {
		j := i
		j = p.Gen0(j, j)
		j = p.Gen1(j, j)
		j = p.Gen2(j, j)
		j = p.Gen3(j, j)
		j = p.Gen4(j, j)
		j = p.Gen5(j, j)
		j = p.Gen6(j, j)
		j = p.Gen7(j, j)
		j = p.Gen8(j, j)
		j = p.Gen9(j, j)
		j = p.Gen10(j, j)
		j = p.Gen11(j, j)
		j = p.Gen12(j, j)
		j = p.Gen13(j, j)
		j = p.Gen14(j, j)
		j = p.Gen15(j, j)
		j = p.Gen16(j, j)
		j = p.Gen17(j, j)
		j = p.Gen18(j, j)
		j = p.Gen19(j, j)
	}
}

func BenchmarkFuncPointersInner(b *testing.B) {
	p := newFuncsInner()
	for i := 0; i < b.N; i++ {
		j := i
		j = p.Gen0(j, j)
		j = p.Gen1(j, j)
		j = p.Gen2(j, j)
		j = p.Gen3(j, j)
		j = p.Gen4(j, j)
		j = p.Gen5(j, j)
		j = p.Gen6(j, j)
		j = p.Gen7(j, j)
		j = p.Gen8(j, j)
		j = p.Gen9(j, j)
		j = p.Gen10(j, j)
		j = p.Gen11(j, j)
		j = p.Gen12(j, j)
		j = p.Gen13(j, j)
		j = p.Gen14(j, j)
		j = p.Gen15(j, j)
		j = p.Gen16(j, j)
		j = p.Gen17(j, j)
		j = p.Gen18(j, j)
		j = p.Gen19(j, j)
	}
}

gen.go

package main

import (
	"fmt"
	"strconv"
	"os"
	"bytes"
)

type Interface interface {
	Gen0(int) int
	Gen1(int) int
	Gen2(int) int
	Gen3(int) int
	Gen4(int) int
	Gen5(int) int
	Gen6(int) int
	Gen7(int) int
	Gen8(int) int
	Gen9(int) int
	Gen10(int) int
	Gen11(int) int
	Gen12(int) int
	Gen13(int) int
	Gen14(int) int
	Gen15(int) int
	Gen16(int) int
	Gen17(int) int
	Gen18(int) int
	Gen19(int) int
}

type FuncPointers struct {
	Gen0  func(int, int) int
	Gen1  func(int, int) int
	Gen2  func(int, int) int
	Gen3  func(int, int) int
	Gen4  func(int, int) int
	Gen5  func(int, int) int
	Gen6  func(int, int) int
	Gen7  func(int, int) int
	Gen8  func(int, int) int
	Gen9  func(int, int) int
	Gen10 func(int, int) int
	Gen11 func(int, int) int
	Gen12 func(int, int) int
	Gen13 func(int, int) int
	Gen14 func(int, int) int
	Gen15 func(int, int) int
	Gen16 func(int, int) int
	Gen17 func(int, int) int
	Gen18 func(int, int) int
	Gen19 func(int, int) int
}

func Gen0(i int, j int) int {
	return i + j
}

func Gen1(i int, j int) int {
	return i + j
}

func Gen2(i int, j int) int {
	return i + j
}

func Gen3(i int, j int) int {
	return i + j
}

func Gen4(i int, j int) int {
	return i + j
}

func Gen5(i int, j int) int {
	return i + j
}

func Gen6(i int, j int) int {
	return i + j
}

func Gen7(i int, j int) int {
	return i + j
}

func Gen8(i int, j int) int {
	return i + j
}

func Gen9(i int, j int) int {
	return i + j
}

func Gen10(i int, j int) int {
	return i + j
}

func Gen11(i int, j int) int {
	return i + j
}

func Gen12(i int, j int) int {
	return i + j
}

func Gen13(i int, j int) int {
	return i + j
}

func Gen14(i int, j int) int {
	return i + j
}

func Gen15(i int, j int) int {
	return i + j
}

func Gen16(i int, j int) int {
	return i + j
}

func Gen17(i int, j int) int {
	return i + j
}

func Gen18(i int, j int) int {
	return i + j
}

func Gen19(i int, j int) int {
	return i + j
}

func newFuncs() *FuncPointers {
	return &FuncPointers{
		Gen0:  Gen0,
		Gen1:  Gen1,
		Gen2:  Gen2,
		Gen3:  Gen3,
		Gen4:  Gen4,
		Gen5:  Gen5,
		Gen6:  Gen6,
		Gen7:  Gen7,
		Gen8:  Gen8,
		Gen9:  Gen9,
		Gen10: Gen10,
		Gen11: Gen11,
		Gen12: Gen12,
		Gen13: Gen13,
		Gen14: Gen14,
		Gen15: Gen15,
		Gen16: Gen16,
		Gen17: Gen17,
		Gen18: Gen18,
		Gen19: Gen19,
	}
}

type FuncPointersInner struct {
	gen0  func(int, int) int
	gen1  func(int, int) int
	gen2  func(int, int) int
	gen3  func(int, int) int
	gen4  func(int, int) int
	gen5  func(int, int) int
	gen6  func(int, int) int
	gen7  func(int, int) int
	gen8  func(int, int) int
	gen9  func(int, int) int
	gen10 func(int, int) int
	gen11 func(int, int) int
	gen12 func(int, int) int
	gen13 func(int, int) int
	gen14 func(int, int) int
	gen15 func(int, int) int
	gen16 func(int, int) int
	gen17 func(int, int) int
	gen18 func(int, int) int
	gen19 func(int, int) int
}

func newFuncsInner() *FuncPointersInner {
	return &FuncPointersInner{
		gen0:  Gen0,
		gen1:  Gen1,
		gen2:  Gen2,
		gen3:  Gen3,
		gen4:  Gen4,
		gen5:  Gen5,
		gen6:  Gen6,
		gen7:  Gen7,
		gen8:  Gen8,
		gen9:  Gen9,
		gen10: Gen10,
		gen11: Gen11,
		gen12: Gen12,
		gen13: Gen13,
		gen14: Gen14,
		gen15: Gen15,
		gen16: Gen16,
		gen17: Gen17,
		gen18: Gen18,
		gen19: Gen19,
	}
}

func (f *FuncPointersInner) Gen0(i int, j int) int {
	return f.gen0(i, j)
}

func (f *FuncPointersInner) Gen1(i int, j int) int {
	return f.gen1(i, j)
}

func (f *FuncPointersInner) Gen2(i int, j int) int {
	return f.gen2(i, j)
}

func (f *FuncPointersInner) Gen3(i int, j int) int {
	return f.gen3(i, j)
}

func (f *FuncPointersInner) Gen4(i int, j int) int {
	return f.gen4(i, j)
}

func (f *FuncPointersInner) Gen5(i int, j int) int {
	return f.gen5(i, j)
}

func (f *FuncPointersInner) Gen6(i int, j int) int {
	return f.gen6(i, j)
}

func (f *FuncPointersInner) Gen7(i int, j int) int {
	return f.gen7(i, j)
}

func (f *FuncPointersInner) Gen8(i int, j int) int {
	return f.gen8(i, j)
}

func (f *FuncPointersInner) Gen9(i int, j int) int {
	return f.gen9(i, j)
}

func (f *FuncPointersInner) Gen10(i int, j int) int {
	return f.gen10(i, j)
}

func (f *FuncPointersInner) Gen11(i int, j int) int {
	return f.gen11(i, j)
}

func (f *FuncPointersInner) Gen12(i int, j int) int {
	return f.gen12(i, j)
}

func (f *FuncPointersInner) Gen13(i int, j int) int {
	return f.gen13(i, j)
}

func (f *FuncPointersInner) Gen14(i int, j int) int {
	return f.gen14(i, j)
}

func (f *FuncPointersInner) Gen15(i int, j int) int {
	return f.gen15(i, j)
}

func (f *FuncPointersInner) Gen16(i int, j int) int {
	return f.gen16(i, j)
}

func (f *FuncPointersInner) Gen17(i int, j int) int {
	return f.gen17(i, j)
}

func (f *FuncPointersInner) Gen18(i int, j int) int {
	return f.gen18(i, j)
}

func (f *FuncPointersInner) Gen19(i int, j int) int {
	return f.gen19(i, j)
}

type switcher struct {
	t int
	n int
}

//go:noinline
func (s switcher) Gen0(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

//go:noinline
func (s switcher) Gen1(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

//go:noinline
func (s switcher) Gen2(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

//go:noinline
func (s switcher) Gen3(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

//go:noinline
func (s switcher) Gen4(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

//go:noinline
func (s switcher) Gen5(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

//go:noinline
func (s switcher) Gen6(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

//go:noinline
func (s switcher) Gen7(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

//go:noinline
func (s switcher) Gen8(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

//go:noinline
func (s switcher) Gen9(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

//go:noinline
func (s switcher) Gen10(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

//go:noinline
func (s switcher) Gen11(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

//go:noinline
func (s switcher) Gen12(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

//go:noinline
func (s switcher) Gen13(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

//go:noinline
func (s switcher) Gen14(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

//go:noinline
func (s switcher) Gen15(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

//go:noinline
func (s switcher) Gen16(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

//go:noinline
func (s switcher) Gen17(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

//go:noinline
func (s switcher) Gen18(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

//go:noinline
func (s switcher) Gen19(i int) int {
	if s.t == 0 {
		return s.n + i + 2
	}
	return s.n + i
}

func header() string {
	return `
package main

`
}

func buildNew(n int) string {
	var buf bytes.Buffer
	buf.WriteString(`func newStruct(i int) Interface {
	switch i {`)
	format := `
	case %v:
		return struct%v{n: i}`
	for i := 0; i < n; i++ {
		buf.WriteString(fmt.Sprintf(format, i, i))
	}

	buf.WriteString(`
	default:
		return struct0{n: i}
	}
}
`)
	return buf.String()
}

func build(id int) string {
	format := `
type %v struct {
	n int
}

//go:noinline
func (s %v) Gen0(i int) int {
	return s.n + i
}

//go:noinline
func (s %v) Gen1(i int) int {
	return s.n + i
}

//go:noinline
func (s %v) Gen2(i int) int {
	return s.n + i
}

//go:noinline
func (s %v) Gen3(i int) int {
	return s.n + i
}

//go:noinline
func (s %v) Gen4(i int) int {
	return s.n + i
}

//go:noinline
func (s %v) Gen5(i int) int {
	return s.n + i
}

//go:noinline
func (s %v) Gen6(i int) int {
	return s.n + i
}

//go:noinline
func (s %v) Gen7(i int) int {
	return s.n + i
}

//go:noinline
func (s %v) Gen8(i int) int {
	return s.n + i
}

//go:noinline
func (s %v) Gen9(i int) int {
	return s.n + i
}

//go:noinline
func (s %v) Gen10(i int) int {
	return s.n + i
}

//go:noinline
func (s %v) Gen11(i int) int {
	return s.n + i
}

//go:noinline
func (s %v) Gen12(i int) int {
	return s.n + i
}

//go:noinline
func (s %v) Gen13(i int) int {
	return s.n + i
}

//go:noinline
func (s %v) Gen14(i int) int {
	return s.n + i
}

//go:noinline
func (s %v) Gen15(i int) int {
	return s.n + i
}

//go:noinline
func (s %v) Gen16(i int) int {
	return s.n + i
}

//go:noinline
func (s %v) Gen17(i int) int {
	return s.n + i
}

//go:noinline
func (s %v) Gen18(i int) int {
	return s.n + i
}

//go:noinline
func (s %v) Gen19(i int) int {
	return s.n + i
}

`
	var args []interface{}
	for i := 0; i < 21; i++ {
		args = append(args, "struct"+strconv.Itoa(id))
	}
	return fmt.Sprintf(format, args...)
}

func main() {
	file := "structs.go"
	os.Remove(file)
	f, err := os.Create(file)
	if err != nil {
		panic(err)
	}
	defer f.Close()

	_, err = f.WriteString(header())
	if err != nil {
		panic(err)
	}

	count := 1000

	for i := 0; i < count; i ++ {
		_, err = f.WriteString(build(i))
		if err != nil {
			panic(err)
		}
	}

	_, err = f.WriteString(buildNew(count))
	if err != nil {
		panic(err)
	}
}

from bitset.

kawasin73 avatar kawasin73 commented on July 23, 2024

インターフェイスの内部実装

インターフェイスの内部実装については、「GoのInterfaceとは何者なのか #golang #go」 で詳しく解説されている。

型ごとに作成される itab 構造体の中にメソッドリストがあり、インターフェイスのメソッド呼び出しではそのリストから検索している。
また、itab 構造体はある型がインターフェイスに代入されるときに作成され(コンパイル時ではない)キャッシュされることでメモリ量と計算の省略化のトレードオフをバランスしている。

また、一次情報として、Russ Cox によって書かれた「Go Data Structures: Interfaces」 がある。

from bitset.

Related Issues (13)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.