日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

android 集成同一interface不同泛型_Dig101:Go之读懂interface的底层设计

發布時間:2023/12/15 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android 集成同一interface不同泛型_Dig101:Go之读懂interface的底层设计 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Dig101: dig more, simplified more and know more

今天我們聊聊萬物皆可為的接口(interface)底層設計。

interface 被定義為一組方法的簽名。

有了它,我們可以訂立方法契約,去抽象和約束實現。

而 Go 的基礎類型,可以認為是沒有實現任何方法的空 interface,也就是萬物皆為的 interface。

(Go 語言沒有泛型,接口可以作為一種替代實現)

接口也被寄予厚望,主力開發 Russ Cox 曾說過:

從語言設計的角度來看,Go 的接口是靜態的,在編譯時檢查過的,在需要時是動態的。如果我可以將 Go 的一個特性導出到其他語言中,那就是接口。Go Data Structures: Interfaces[1]

那到底 interface 是怎么設計的底層結構呢?

又怎么支持的duck typing[2]

在類型斷言時又發生了什么?

帶著這些問題,我們往下看

文章目錄

  • 0x01 底層結構一樣么

    • eface

    • iface

  • 0x02 類型如何相互轉換

    • convXXX 的命名

    • 起初的 convT2{I,E} 和 convI2I

    • 針對類型優化后的 convXXX

  • 0x03 類型斷言如何實現

    • 查表是否匹配

    • 嘗試插入更新

    • 動態判定效率優化

0x01 底層結構一樣么

我們知道定義接口有這兩種方式,那他們底層結構是一樣的么?

// 方式1
var a interface{}
// 方式2
type Stringer interface {
String() string
}
var b Stringer

答案是【不一樣】

我們用 gdb 打印下對應類型(gdb 相關見 Tips-如何優雅的使用GDB調試Go)

// 空接口類型// 有函數定義的接口類型// itable相關類型

以此可見 Go 內部定義了兩種 interface(但都是兩個機器字)

eface

空接口,指沒有定義方法的接口

內部存儲了構造類型(concrete type)type和data

eface

iface

有方法的接口

有了相比eface的type更豐富的itab字段,其中記錄了構造類型及所實現的 interface 類型的類型和方法

iface

0x02 類型如何相互轉換

如下代碼,當我們做接口賦值時,Go 又會怎樣填充底層結構呢?

type Binary uint64
func (i Binary) String() string {
return strconv.Itoa(int(i))
}

func conversion() {
var b Stringer
var i Binary = 1
b = i // <= 這里發生了什么
println(b.String())
}

gdb 進到 b = i 這一步,會發現他調用了runtime/iface.go:convT64方法實現 iface 的賦值

查閱源碼,會發現很多convXXX函數, 他們是干什么的?

convXXX 的命名

convFrom2To 指代 To=From 的轉換

From 和 To 的類型有三種:(參見cmd/compile/internal/types/type.go:Tie)

  • E (eface)
  • I (iface)
  • T (Type)

這一堆函數看的人眼暈,但參照提交specialize convT2x, don't alloc for zero vals[3]深入分析,就會清晰許多

起初的 convT2{I,E} 和 convI2I

最初只有 convT2{I,E} 和 convI2I

主要實現分配內存(newobject),然后拷貝賦值(typedmemmove)

convI2I 還會有getitab, 具體是什么我們后邊類型斷言時說

然后也在調用他們前(walkexpr)做了優化

  • 減少值拷貝

ToType 為類指針(pointer-shaped)或者一個機器字內(int)的話,可以直接存入 interface 的 data 字段(主要優化在這里)

pointer-shaped類型: ptr, chan, map, func, unsafe.Pointer

再輔以 type 的存儲,就只是兩個字(two-word)的拷貝

  • 減少內存分配

零值,bool/byte 可以不用分配內存,而用已存在值(zerobase,staticbytes)

只讀的全局變量(readonly global)直接可以用

1kb 以內,不escape到堆上,非interface的變量可以使用棧上分配的臨時變量(stack temporary initialized)

這類 value 最后以取地址形式轉化為 interface:{type/itab, &value}.

  • interface 轉空接口(eface)

可以丟棄除type以外的itab

tmp = i.itab
if tmp != nil {
tmp = tmp.type
}
e = iface{tmp, i.data}

針對類型優化后的 convXXX

但這里會有一些可以優化的點,如:

  • 分配內存是否可以需要清零?

類指針的類型需要清零,不然內存可能有臟數據

但無指針類型(pointer-free)如拷貝時直接可以覆蓋對應內存則不需要

如int其拷貝在一個機器字內完成,不需要分配時清零 (32 位系統上不調用convT64,就可以保證訪問內存是安全的原子操作)

  • 是否可以簡化值拷貝?

int,string,slice這些 Type 分配的x拷貝val時,可以簡化為 *(*Type)(x) = val

  • 拷貝內存是否可以不增加 gc 調用(寫屏障)?

按 ToType 類型是否含指針區分 類指針類型(pointer-shaped): convT2{E,I} 需要拷貝時 gc 調用(typedmemmove)

無指針類型(pointer-free): convT2{E,I}noptr 不需要拷貝時 gc 調用(memmove)

這樣一看就明白這些函數的用意了,還是為了針對性的提高轉化效率

最后結合其調用處convXXX列表如下:

// cmd/compile/internal/gc/walk.go:walkexpr
case OCONVIFACE:
...
fnname, needsaddr := convFuncName(fromType, toType)
fnnamefromTypeneedsaddr
convI2Iiface
convT{16,
32,64}
整型數據
(無指針,
機器字內)
convTstringstring
convTsliceslice
convT2EType
convT2Enoptr無指針
Type
convT2IType
convT2Inoptr無指針
Type

不會存在 convE2E 和 convE2I?

needsaddr: 類型不含指針,大小大于 64 位字或未知大小時,使用值的地址來存

0x03 類型斷言如何實現

interface 支持類型斷言,來動態判斷其構造類型,

判定成功可返回對應構造類型,便于調用其方法

可構造類型實現 interface 不需要顯示聲明,

那如下代碼是怎么確定 interface b(構造類型是Binary)實現Stringer呢?

type Binary uint64

func (i Binary) String() string {
return fmt.Sprint(i)
}

func typeAssert() {
var b interface{} = Binary(1)
v, ok := b.(Stringer)
println(v, ok)
}

調試后會發現,其調用了assertE2I2

這里函數命名有兩類,如下

assertE2I: v := eface1.(iface1)

assertE2I2: v,ok := eface1.(iface1)

這里有一點,類型斷言非 v,ok 方式的,斷言失敗會 panic)

原來其內部進行了itab表(itabTable)查詢 interface 和構造類型的映射表,如果匹配則說明實現

下邊代碼分析如下

首先初始 512 個 entry 的表

const itabInitSize = 512
type itabTableType struct {
// 上限
size uintptr
// 當前用量
count uintptr
entries [itabInitSize]*itab
}

查表是否匹配

在類型斷言中調用 getitab(inter, typ, canfail) 查表

  • 先不加鎖 atomic 讀取 itabTable,找到返回
  • 未找到加鎖再查一遍,找到返回
  • 還沒有就創建一個 itab 添加到表中,添加完后解鎖
  • 期間如果判定不匹配則按是否可以 panic(canfail)返回

其中查表用到 itabTable.find(inter, typ),

插入用到 itabAdd(m)

嘗試插入更新

  • 插入前需先用m.inter/m._type pair 初始化 m.fun 數組,不匹配則m.fun[0]==0

(m.fun 類型 [1]uintptr,實際指向是大小為接口定義方法數的方法數組。詳見 func (m *itab) init())

  • 用量 count 超過上限的 75%觸發擴容,大小為 2 倍以上(要向上內存對齊),擴容后更新 itabTable 是原子操作

  • 以 itab m 的 interface 類型和構造類型的 hash 計算對應 itabTable 的起始偏移,然后插入到其后第一個不為空的 entry。如果已存在則直接返回

這里用到了開放地址探測法,公式是:

h(i) = h0 + i*(i+1)/2 mod 2^k

具體插入用到 itabTable.add(m)

這里和其實 map 插入的邏輯很相似

動態判定效率優化

不過,這里有一個問題?

假定,interface 定義了ni個方法,構造類型實現nt個方法,

常規匹配構造類型是否實現全部ni個方法需要兩層遍歷,復雜度為O(ni*nt)

這樣在初始化itab.fun或類型斷言匹配時效率會比較低。

Go 設計時也考慮了這個問題,把復雜度降低為O(ni+nt)

這也是使用 hashtable 的原因之一:

  • 首先 interface 的函數定義列表itab.inter.mhdr和構造類型的函數列表itab.fun都是按函數名排好序的

  • 這樣第一次 itab 初始化時,判定構造類型是否實現函數列表可以O(ni+nt)內遍歷完成

  • 然后用開放地址探測法更新到 itabtable 中,查詢時也可以用同樣的方式定位到此 itab 是否存在。

兩個(有序)列表的遍歷匹配代碼精簡如下:

// runtime/iface.go:init()
j:=0
imethods:
// 遍歷interface定義函數列表
for k := 0; k < ni; k++ {
// 遍歷構造類型函數列表
for ; j < nt; j++ {
// 如果兩者類型(type),包路徑(pkgpath),函數名(name)匹配
if xxx {
// 將方法記錄到fun0(最終全匹配則賦值給 m.fun)
continue imethods
}
}
// 未全匹配
m.fun[0] = 0
}
m.fun[0] = uintptr(fun0)

總結一下 interface 的底層設計:

  • interface 分為空接口(eface)和接口(iface)兩類,但都是兩機器字(two-word)存儲結構
  • interface 轉換中針對不同類型做了優化,主要集中于提升內存分配和值拷貝效率
  • interface 類型斷言時動態判定,利用有序列表遍歷+全局哈希表表緩存優化判定效率

See More:官方解釋?InterfaceSlice[4]?為什么不能直接轉化


最后留個問題:

下邊這段轉換代碼內部沒有調convT64,為什么?

var b Stringer = Binary(1)
_ = b.String()

這個問題下一篇文章再來給出解答。

本文代碼見 NewbMiao/Dig101-Go[5]

參考資料

[1]

Go Data Structures: Interfaces: https://research.swtch.com/interfaces

[2]

duck typing: https://en.wikipedia.org/wiki/Duck_typing

[3]

specialize convT2x, don't alloc for zero vals: https://go-review.googlesource.com/c/go/+/36476

[4]

InterfaceSlice: https://github.com/golang/go/wiki/InterfaceSlice

[5]

NewbMiao/Dig101-Go: https://github.com/NewbMiao/Dig101-Go/blob/master/types/interface/interface.go


推薦閱讀

  • Dig101: Go之for-range排坑指南
  • Dig101: Go之靈活的slice
  • Dig101: Go之string那些事
  • Dig101:Go之讀懂map的底層設計
  • Dig101:Go之聊聊struct的內存對齊
  • Tips-如何優雅的使用GDB調試Go

原創不易,如果有用,歡迎轉發、點個在看,分享給更多人。

微信內外鏈不能跳轉,戳閱讀原文查看原文中參考資料


????歡迎關注我,一位愛折騰的開發(奶爸):熱衷搬磚、價投。

總結

以上是生活随笔為你收集整理的android 集成同一interface不同泛型_Dig101:Go之读懂interface的底层设计的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。