実行時の型発見の方法として、Goで型アサーション/型スイッチを使用するのはどれくらい遅いですか?
たとえば、C/C++では、実行時に型を検出するとパフォーマンスが低下すると聞きました。それをバイパスするには、通常、型メンバーをクラスに追加します。そのため、キャストする代わりにこれらと比較できます。
これについての明確な答えは、www全体で見つかりませんでした。
ここに私が尋ねているものの例があります-これはfastと考えられていますか?
func question(anything interface{}) {
switch v := anything.(type) {
case string:
fmt.Println(v)
case int32, int64:
fmt.Println(v)
case SomeCustomType:
fmt.Println(v)
default:
fmt.Println("unknown")
}
}
ベンチマークテストを作成して確認するのは非常に簡単です。 http://play.golang.org/p/E9H_4K2J9-
package main
import (
"testing"
)
type myint int64
type Inccer interface {
inc()
}
func (i *myint) inc() {
*i = *i + 1
}
func BenchmarkIntmethod(b *testing.B) {
i := new(myint)
incnIntmethod(i, b.N)
}
func BenchmarkInterface(b *testing.B) {
i := new(myint)
incnInterface(i, b.N)
}
func BenchmarkTypeSwitch(b *testing.B) {
i := new(myint)
incnSwitch(i, b.N)
}
func BenchmarkTypeAssertion(b *testing.B) {
i := new(myint)
incnAssertion(i, b.N)
}
func incnIntmethod(i *myint, n int) {
for k := 0; k < n; k++ {
i.inc()
}
}
func incnInterface(any Inccer, n int) {
for k := 0; k < n; k++ {
any.inc()
}
}
func incnSwitch(any Inccer, n int) {
for k := 0; k < n; k++ {
switch v := any.(type) {
case *myint:
v.inc()
}
}
}
func incnAssertion(any Inccer, n int) {
for k := 0; k < n; k++ {
if newint, ok := any.(*myint); ok {
newint.inc()
}
}
}
AMD64マシンでは、次のタイミングが得られます。
$ go test -bench=.
BenchmarkIntmethod 1000000000 2.71 ns/op
BenchmarkInterface 1000000000 2.98 ns/op
BenchmarkTypeSwitch 100000000 16.7 ns/op
BenchmarkTypeAssertion 100000000 13.8 ns/op
したがって、タイプスイッチまたはタイプアサーションを介したメソッドへのアクセスは、メソッドを直接またはインターフェイスを介して呼び出すよりも約5〜6倍遅いようです。
C++の方が遅いのか、このスローダウンがアプリケーションに耐えられるのかはわかりません。
私は自分でシリティンガの答えを検証し、TypeAssertionのチェックを削除することでそれが速くなるかどうかを確認したかったのです。ベンチマークに次を追加しました。
func incnAssertionNoCheck(any Inccer, n int) {
for k := 0; k < n; k++ {
any.(*myint).inc()
}
}
func BenchmarkTypeAssertionNoCheck(b *testing.B) {
i := new(myint)
incnAssertionNoCheck(i, b.N)
}
私のマシンでベンチマークを再実行しました。
BenchmarkIntmethod-12 2000000000 1.77 ns/op
BenchmarkInterface-12 1000000000 2.30 ns/op
BenchmarkTypeSwitch-12 500000000 3.76 ns/op
BenchmarkTypeAssertion-12 2000000000 1.73 ns/op
BenchmarkTypeAssertionNoCheck-12 2000000000 1.72 ns/op
したがって、タイプの切り替えにかかるコストは、大幅に Go 1.4(私はsiritingaを使用していると仮定)からGo 1.6(私が使用している)に低下したようです。タイプスイッチの場合は2倍遅く、タイプアサーションの場合は遅くなりません(チェックありまたはなし)。
Go 1.9を使用した私の結果
BenchmarkIntmethod-4 1000000000 2.42 ns/op
BenchmarkInterface-4 1000000000 2.84 ns/op
BenchmarkTypeSwitch-4 1000000000 2.29 ns/op
BenchmarkTypeAssertion-4 1000000000 2.14 ns/op
BenchmarkTypeAssertionNoCheck-4 1000000000 2.34 ns/op
型アサーションは今でははるかに高速ですが、型チェックを削除する最も興味深いのはそれが遅くなることです。
私はラップトップ(go1.7.3 linux/AMD64)で@siritingaによるベンチの例を実行し、次の結果を得ました。
$ go test -bench .
BenchmarkIntmethod-4 2000000000 1.99 ns/op
BenchmarkInterface-4 1000000000 2.30 ns/op
BenchmarkTypeSwitch-4 2000000000 1.80 ns/op
BenchmarkTypeAssertion-4 2000000000 1.67 ns/op
TL; DR:それは本当に型の分布に依存しますが、型が通常のチャンクで現れることが確実でない限り、インターフェースが最も安全な選択です。また、コードが頻繁に実行されない場合、分岐予測子もウォームアップされないことを考慮してください。
長い説明:
Darwin/AMD64のgo1.9.2で
BenchmarkIntmethod-4 2000000000 1.67 ns/op
BenchmarkInterface-4 2000000000 1.89 ns/op
BenchmarkTypeSwitch-4 2000000000 1.26 ns/op
BenchmarkTypeAssertion-4 2000000000 1.41 ns/op
BenchmarkTypeAssertionNoCheck-4 2000000000 1.61 ns/op
ここで注意すべき重要なことは、ブランチが1つしかないタイプスイッチは、インターフェースの使用とあまり公平な比較ではないということです。 CPUブランチプレディクタは、非常に高温になり、非常に高速になり、非常に良い結果が得られます。より良いベンチマークは、擬似ランダムタイプと擬似ランダムレシーバーとのインターフェイスを使用します。明らかに、静的メソッドのディスパッチを削除し、タイプスイッチと比較してインターフェイスだけに固執する必要があります(多くのifステートメントが必要になるため、タイプアサーションもあまり意味がなくなり、タイプスイッチを使用する代わりに誰も記述しません)。コードは次のとおりです。
package main
import (
"testing"
)
type myint0 int64
type myint1 int64
type myint2 int64
type myint3 int64
type myint4 int64
type myint5 int64
type myint6 int64
type myint7 int64
type myint8 int64
type myint9 int64
type DoStuff interface {
doStuff()
}
func (i myint0) doStuff() {
i += 0
}
func (i myint1) doStuff() {
i += 1
}
func (i myint2) doStuff() {
i += 2
}
func (i myint3) doStuff() {
i += 3
}
func (i myint4) doStuff() {
i += 4
}
func (i myint5) doStuff() {
i += 5
}
func (i myint6) doStuff() {
i += 6
}
func (i myint7) doStuff() {
i += 7
}
func (i myint8) doStuff() {
i += 8
}
func (i myint9) doStuff() {
i += 9
}
// Randomly generated
var input []DoStuff = []DoStuff{myint0(0), myint1(0), myint1(0), myint5(0), myint6(0), myint7(0), myint6(0), myint9(0), myint7(0), myint7(0), myint6(0), myint2(0), myint9(0), myint0(0), myint2(0), myint3(0), myint5(0), myint1(0), myint4(0), myint0(0), myi
nt4(0), myint3(0), myint9(0), myint3(0), myint9(0), myint5(0), myint0(0), myint0(0), myint8(0), myint1(0)}
func BenchmarkInterface(b *testing.B) {
doStuffInterface(b.N)
}
func BenchmarkTypeSwitch(b *testing.B) {
doStuffSwitch(b.N)
}
func doStuffInterface(n int) {
for k := 0; k < n; k++ {
for _, in := range input {
in.doStuff()
}
}
}
func doStuffSwitch(n int) {
for k := 0; k < n; k++ {
for _, in := range input {
switch v := in.(type) {
case *myint0:
v.doStuff()
case *myint1:
v.doStuff()
case *myint2:
v.doStuff()
case *myint3:
v.doStuff()
case *myint4:
v.doStuff()
case *myint5:
v.doStuff()
case *myint6:
v.doStuff()
case *myint7:
v.doStuff()
case *myint8:
v.doStuff()
case *myint9:
v.doStuff()
}
}
}
}
そして結果:
go test -bench .
goos: darwin
goarch: AMD64
pkg: test
BenchmarkInterface-4 20000000 74.0 ns/op
BenchmarkTypeSwitch-4 20000000 119 ns/op
PASS
ok test 4.067s
タイプが多くなり、分布がランダムになるほど、勝利インターフェイスは大きくなります。
この不均衡を示すために、コードを変更して、常に同じタイプを選択するのではなく、ランダムな選択をベンチマークしました。この場合、タイプスイッチは再び高速になりますが、インターフェイスは同じ速度になります。コードを次に示します。
package main
import (
"testing"
)
type myint0 int64
type myint1 int64
type myint2 int64
type myint3 int64
type myint4 int64
type myint5 int64
type myint6 int64
type myint7 int64
type myint8 int64
type myint9 int64
type DoStuff interface {
doStuff()
}
func (i myint0) doStuff() {
i += 0
}
func (i myint1) doStuff() {
i += 1
}
func (i myint2) doStuff() {
i += 2
}
func (i myint3) doStuff() {
i += 3
}
func (i myint4) doStuff() {
i += 4
}
func (i myint5) doStuff() {
i += 5
}
func (i myint6) doStuff() {
i += 6
}
func (i myint7) doStuff() {
i += 7
}
func (i myint8) doStuff() {
i += 8
}
func (i myint9) doStuff() {
i += 9
}
// Randomly generated
var randInput []DoStuff = []DoStuff{myint0(0), myint1(0), myint1(0), myint5(0), myint6(0), myint7(0), myint6(0), myint9(0), myint7(0), myint7(0), myint6(0), myint2(0), myint9(0), myint0(0), myint2(0), myint3(0), myint5(0), myint1(0), myint4(0), myint0(0),
myint4(0), myint3(0), myint9(0), myint3(0), myint9(0), myint5(0), myint0(0), myint0(0), myint8(0), myint1(0)}
var oneInput []DoStuff = []DoStuff{myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0),
myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0)}
func BenchmarkRandomInterface(b *testing.B) {
doStuffInterface(randInput, b.N)
}
func BenchmarkRandomTypeSwitch(b *testing.B) {
doStuffSwitch(randInput, b.N)
}
func BenchmarkOneInterface(b *testing.B) {
doStuffInterface(oneInput, b.N)
}
func BenchmarkOneTypeSwitch(b *testing.B) {
doStuffSwitch(oneInput, b.N)
}
func doStuffInterface(input []DoStuff, n int) {
for k := 0; k < n; k++ {
for _, in := range input {
in.doStuff()
}
}
}
func doStuffSwitch(input []DoStuff, n int) {
for k := 0; k < n; k++ {
for _, in := range input {
switch v := in.(type) {
case *myint0:
v.doStuff()
case *myint1:
v.doStuff()
case *myint2:
v.doStuff()
case *myint3:
v.doStuff()
case *myint4:
v.doStuff()
case *myint5:
v.doStuff()
case *myint6:
v.doStuff()
case *myint7:
v.doStuff()
case *myint8:
v.doStuff()
case *myint9:
v.doStuff()
}
}
}
}
結果は次のとおりです。
BenchmarkRandomInterface-4 20000000 76.9 ns/op
BenchmarkRandomTypeSwitch-4 20000000 115 ns/op
BenchmarkOneInterface-4 20000000 76.6 ns/op
BenchmarkOneTypeSwitch-4 20000000 68.1 ns/op
あなたの
_switch v := anything.(type) {
case SomeCustomType:
fmt.Println(v)
...
_
_SomeCustomType.Fields
_やfmt.Println(v)
のようなメソッドが必要ない場合、
_switch anything.(type) { //avoid 'v:= ' interface conversion, only assertion
case SomeCustomType:
fmt.Println("anything type is SomeCustomType", anything)
...
_
約2倍高速にする必要があります