對於 Go 中的實用函式我有話說

taowen發表於2017-07-09

目標:讓 Go 中支援類似下面這樣的函式

func Max(collection ...interface{}) interface{}

問題:如果用反射實現的話,效率是問題。

解決方案:json.Unmarshal 就是用反射實現的,jsoniter 通過用 unsafe.Pointer 加上快取的 decoder 實現了 6 倍的速度提升。所以嘗試用同樣的技術原理,寫一個概念驗證的原型 https://github.com/v2pro/wombat

實現的 API 類似這樣

import (
    "testing"
    "github.com/stretchr/testify/require"
    "github.com/v2pro/plz"
)

func Test_max_min(t *testing.T) {
    should := require.New(t)
    should.Equal(3, plz.Max(1, 3, 2))
    should.Equal(1, plz.Min(1, 3, 2))

    type User struct {
        Score int
    }
    should.Equal(User{3}, plz.Max(
        User{1}, User{3}, User{2},
        "Score"))
}

其中的原理是從 interface{} 中提取 unsafe.Pointer。 然後用 Accessor 獲得具體的值。這個 Accessor 的概念是和類對應的,而不是和值對應的。也就是相當於 type.GetIntValue(interface{}) 這樣的意思。這個在 Java 的反射 API 中是支援的,而 Go 沒有提供這樣的 API。利用 Accessor 我們可以一次性計算好整個任務,然後快取起來。這樣執行期的成本大概就是虛擬函式呼叫的成本。

Accessor 的介面定義

type Accessor interface {
    // === static ===
    fmt.GoStringer
    Kind() Kind
    // map
    Key() Accessor
    // array/map
    Elem() Accessor
    // struct
    NumField() int
    Field(index int) StructField
    // array/struct
    RandomAccessible() bool
    New() (interface{}, Accessor)

    // === runtime ===
    IsNil(ptr unsafe.Pointer) bool
    // variant
    VariantElem(ptr unsafe.Pointer) (elem unsafe.Pointer, elemAccessor Accessor)
    InitVariant(ptr unsafe.Pointer, template Accessor) (elem unsafe.Pointer, elemAccessor Accessor)
    // map
    MapIndex(ptr unsafe.Pointer, key unsafe.Pointer) (elem unsafe.Pointer) // only when random accessible
    SetMapIndex(ptr unsafe.Pointer, key unsafe.Pointer, elem unsafe.Pointer) // only when random accessible
    IterateMap(ptr unsafe.Pointer, cb func(key unsafe.Pointer, elem unsafe.Pointer) bool)
    FillMap(ptr unsafe.Pointer, cb func(filler MapFiller))
    // array/struct
    ArrayIndex(ptr unsafe.Pointer, index int) (elem unsafe.Pointer) // only when random accessible
    IterateArray(ptr unsafe.Pointer, cb func(index int, elem unsafe.Pointer) bool)
    FillArray(ptr unsafe.Pointer, cb func(filler ArrayFiller))
    // primitives
    Skip(ptr unsafe.Pointer) // when the value is not needed
    String(ptr unsafe.Pointer) string
    SetString(ptr unsafe.Pointer, val string)
    Bool(ptr unsafe.Pointer) bool
    SetBool(ptr unsafe.Pointer, val bool)
    Int(ptr unsafe.Pointer) int
    SetInt(ptr unsafe.Pointer, val int)
    Int8(ptr unsafe.Pointer) int8
    SetInt8(ptr unsafe.Pointer, val int8)
    Int16(ptr unsafe.Pointer) int16
    SetInt16(ptr unsafe.Pointer, val int16)
    Int32(ptr unsafe.Pointer) int32
    SetInt32(ptr unsafe.Pointer, val int32)
    Int64(ptr unsafe.Pointer) int64
    SetInt64(ptr unsafe.Pointer, val int64)
    Uint(ptr unsafe.Pointer) uint
    SetUint(ptr unsafe.Pointer, val uint)
    Uint8(ptr unsafe.Pointer) uint8
    SetUint8(ptr unsafe.Pointer, val uint8)
    Uint16(ptr unsafe.Pointer) uint16
    SetUint16(ptr unsafe.Pointer, val uint16)
    Uint32(ptr unsafe.Pointer) uint32
    SetUint32(ptr unsafe.Pointer, val uint32)
    Uint64(ptr unsafe.Pointer) uint64
    SetUint64(ptr unsafe.Pointer, val uint64)
    Float32(ptr unsafe.Pointer) float32
    SetFloat32(ptr unsafe.Pointer, val float32)
    Float64(ptr unsafe.Pointer) float64
    SetFloat64(ptr unsafe.Pointer, val float64)
}

利用這個 Accessor 可以幹很多事情,除了各種函數語言程式設計常用的 utility(map/filter/sorted/...)之外。還可以實現一個 plz.Copy 的函式

func Copy(dst, src interface{}) error

Copy 可以用於各種物件繫結的場景

  • Go 不同型別物件之間的值拷貝(struct&map 互相轉換,相容指標)
  • JSON 編解碼
  • 拷貝 http.Request 到我的 struct 上
  • 拷貝 sql rows 到我的 struct 上
  • Mysql/thrift/redis 等其他協議的編解碼

還可以用來實現 plz.Validate 的函式

func Validate(obj interface{}) error

甚至有可能的話,還可以把 .net 的 linq 的概念拿過來

func Query(obj interface{}, query string) (result interface{}, err error)

當然這個工作量非常浩大,比一個 JSON 解析庫繁瑣得多。現在只實現了幾個概念原型:

用興趣的朋友可以來發 issue:https://github.com/v2pro/wombat/issues

更多原創文章乾貨分享,請關注公眾號
  • 對於 Go 中的實用函式我有話說
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章