Upgrade to Pro — share decks privately, control downloads, hide ads and more …

GoのInterface内部構造から学ぶ!最高パフォーマンスを出すコード設計

 GoのInterface内部構造から学ぶ!最高パフォーマンスを出すコード設計

実務でGoを書いていると、「nilを入れたはずなのにエラー扱いされる」「原因の見えないメモリ確保が積み上がる」といった場面に出会います。本セッションでは、interfaceを内部構造から捉え直し、こうした問題を避けながら性能を引き出すコード設計の考え方を共有します。

Go  Night Talks 2026 - After Conference 2026/9/18(金)
https://mercari.connpass.com/event/401987/

Avatar for Yappli Developers

Yappli Developers

September 18, 2026

More Decks by Yappli Developers

Other Decks in Technology

Transcript

  1. SPEAKER プロダクト開発本部 CX開発部 添川知紘 • • • • • Yappli、最⼩のエンジニア

    ⼤⼿私鉄の経理給与業務を5年ほど経験 そこから⼀念発起してエンジニアにキャ リアチェンジ!! 前職ではフルスタックでrubyをメインで 開発をしてました。 現在、エンジニア3年⽬です。
  2. ⼈⽣初のinterfaceとの出会いと歩み寄り • ヤプリに⼊社(2ヶ⽉前)して初めてGoに触れる ◦ 静的型付け⾔語、初めてだな。 ◦ interface??? • 「interfaceって何?」をAIに聞いてみた ◦

    「コードの型(⾦型‧テンプレート)」のようなもの! ◦ 実装漏れを無くしたい、どんなメソットを使⽤するかの設計図に なる
  3. interfaceを使っていくなかで謎が発⽣! var p *MyStruct = nil // 中⾝は「完全に nil」 var

    i MyInterface = p // インターフェースに⼊れる if i == nil { 構造体にnilを⼊れたのに nilと認識されない?? fmt.Println("nil だよ!") // 実⾏されない!? } else { fmt.Println("nil じゃないよ!") // ← なぜかこっちが実⾏される! } なんだこれは。。 バグ??
  4. インターフェースの内部構造 • 「型情報」と「値情報」の2つのポインタで構成 src/runtime/runtime2.go // interfaceの内部構造 type iface struct {

    tab *itab data unsafe.Pointer } // interface{}/any // (empty interface)の内部構造 type eface struct { _type *_type data unsafe.Pointer } 「型情報」と「値情報」が両⽅nilの時だけ、nilとイコールであると判断される
  5. Typed Nil Problem の対処 正しい書き⽅ func DoSomething() error { var

    err *MyCustomError // エラーが起きた時だけ構造体を作る if somethingFailed { err = &MyCustomError{Code: 500, Message: "失敗しました"} return err } // × return err(絶対にダメ) // ◦ エラーがないなら明⽰的に nil を返す return nil }
  6. 誰が判断し、どこで確保するのか コンパイラのエスケープ解析が「スタックでは寿命が⾜りない」と判断します。 実際の確保は runtime/iface.go の mallocgc 呼び出しです。 // ポインタを含まない値 //

    ポインタを含む値 // src/runtime/iface.go // src/runtime/iface.go func convTnoptr(t *_type, func convT(t *_type, } v unsafe.Pointer) unsafe.Pointer { v unsafe.Pointer) unsafe.Pointer { // ヒープ確保 // ヒープ確保 x := mallocgc(t.Size_, t, false) x := mallocgc(t.Size_, t, true) memmove(x, v, t.Size_) typedmemmove(t, x, v) return x return x }
  7. ① 具象型へ — 型をそのまま持つ // Before: []any items := []any{1,

    2, 3} for _, item := range items { sum += item.(int) } // After: []int items := []int{1, 2, 3} for _, item := range items { sum += item } 型が決まっているなら、まず具象型で持つ。 型アサーションと boxing を減らせる場合がある。
  8. ① Generics — 型を保ったまま共通化 // 型を保ったまま共通化 func total[T ~int |

    ~int64](items []T) T { var sum T for _, item := range items { sum += item } return sum } 共通化が必要なときは Generics を検討する。 効果は -benchmem で必ず確認。
  9. ① 補⾜:Go 1.27 の Generic Methods Go 1.27から、メソッドにも型パラメータを付けられる。 type List[E

    any] []E func (l List[E]) Apply[F any](f func(E) F) List[F] { r := make(List[F], len(l)) for i, v := range l { r[i] = f(v) } return r } 具体型に関連する処理を、型のそばに置ける。 ただし interface のメソッドは、まだGenericにできない。 type Client interface { GetMember[T any](...) } // ×