[Golang語言社群]--提高 golang 的反射效能
阿新 • • 發佈:2019-02-12
golang 的反射很慢。這個和它的 api 設計有關。在 java 裡面,我們一般使用反射都是這樣來弄的。
Field field = clazz.getField("hello");
field.get(obj1);
field.get(obj2);
這個取得的反射物件型別是 java.lang.reflect.Field。它是可以複用的。只要傳入不同的obj,就可以取得這個obj上對應的 field。但是 golang 的反射不是這樣設計的
type_ := reflect.TypeOf(obj)
field, _ := type_.FieldByName("hello")
這裡取出來的 field 物件是 reflect.StructField 型別,但是它沒有辦法用來取得對應物件上的值。如果要取值,得用另外一套對object,而不是type的反射
type_ := reflect.ValueOf(obj)
fieldValue := type_.FieldByName("hello")
這裡取出來的 fieldValue 型別是 reflect.Value,它是一個具體的值,而不是一個可複用的反射物件了。
這就很蛋疼了!每次反射都需要malloc這個reflect.Value結構體。golang的反射效能怎麼可能快?
Jsoniter 是 golang 實現的,基於反射的 JSON 解析器。其實現原理是用 reflect.Type 得出來的資訊來直接做反射,而不依賴於 reflect.ValueOf。具體是怎麼實現的呢?
結構體
先解決一個小問題。怎麼利用 reflect.StructField 取得物件上的值?
fieldPtr := uintptr(structPtr) + field.Offset
在 reflect.StructField 上有一個 Offset 的屬性。利用這個可以計算出欄位的指標值。我們可以寫一個小測試來驗證,這個是對的。
type TestObj struct {
field1 string
}
struct_ := &TestObj{}
field, _ := reflect.TypeOf(struct_).Elem().FieldByName("field1" )
field1Ptr := uintptr(unsafe.Pointer(struct_)) + field.Offset
*((*string)(unsafe.Pointer(field1Ptr))) = "hello"
fmt.Println(struct_)
打印出來的訊息是 &{hello}
獲取 interface{} 的指標
如果對應的結構體是以 interface{} 傳進來的。還需要從 interface{} 上取得結構體的指標
type TestObj struct {
field1 string
}
struct_ := &TestObj{}
structInter := (interface{})(struct_)
// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
typ *struct{}
word unsafe.Pointer
}
structPtr := (*emptyInterface)(unsafe.Pointer(&structInter)).word
field, _ := reflect.TypeOf(structInter).Elem().FieldByName("field1")
field1Ptr := uintptr(structPtr) + field.Offset
*((*string)(unsafe.Pointer(field1Ptr))) = "hello"
fmt.Println(struct_)
Slice
搞定了結構體,接下來就是處理slice型別了。
type sliceHeader struct {
Data unsafe.Pointer
Len int
Cap int
}
slice 的祕訣在於取出指向陣列頭部的指標,然後具體的元素,通過偏移量來計算。
slice := []string{"hello", "world"}
type sliceHeader struct {
Data unsafe.Pointer
Len int
Cap int
}
header := (*sliceHeader)(unsafe.Pointer(&slice))
fmt.Println(header.Len)
elementType := reflect.TypeOf(slice).Elem()
secondElementPtr := uintptr(header.Data) + elementType.Size()
*((*string)(unsafe.Pointer(secondElementPtr))) = "!!!"
fmt.Println(slice)
打印出來的內容
2
[hello !!!]
Map
對於 Map 型別來說,沒有 reflect.ValueOf 之外的獲取其內容的方式。所以還是隻能老老實實地用golang自帶的值反射api。