中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么讓Go語言中的反射加快

發布時間:2022-08-29 17:02:13 來源:億速云 閱讀:119 作者:iii 欄目:開發技術

這篇文章主要介紹“怎么讓Go語言中的反射加快”,在日常操作中,相信很多人在怎么讓Go語言中的反射加快問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”怎么讓Go語言中的反射加快”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

切入點案例

我們以一個簡單的案例為切入點,定義一個結構體 SimpleStruct,它包括兩個 int 類型字段 A 和 B。

type SimpleStruct struct {
    A int
    B int
}

假如我們接收到了 JSON 數據 {"B": 42},想要對其進行解析并且將字段 B 設置為 42。

在下文,我們將編寫一些函數來實現這一點,它們都會將 B 設置為 42。

如果我們的代碼只適用于 SimpleStruct,這完全是不值一提的。

func populateStruct(in *SimpleStruct) {
    in.B = 42
}

反射基本版

但是,如果我們是要做一個 JSON 解析器,這意味著我們并不能提前知道結構類型。我們的解析器代碼需要接收任何類型的數據。

在 Go 中,這通常意味著需要采用 interface{} (空接口)參數。然后我們可以使用 reflect 包檢查通過空接口參數傳入的值,檢查它是否是指向結構體的指針,找到字段 B 并用我們的值填充它。

代碼將如下所示。

func populateStructReflect(in interface{}) error {
 val := reflect.ValueOf(in)
 if val.Type().Kind() != reflect.Ptr {
  return fmt.Errorf("you must pass in a pointer")
 }
 elmv := val.Elem()
 if elmv.Type().Kind() != reflect.Struct {
  return fmt.Errorf("you must pass in a pointer to a struct")
 }

 fval := elmv.FieldByName("B")
 fval.SetInt(42)

 return nil
}

讓我們通過基準測試看看它有多快。

func BenchmarkPopulateReflect(b *testing.B) {
 b.ReportAllocs()
 var m SimpleStruct
 for i := 0; i < b.N; i++ {
  if err := populateStructReflect(&m); err != nil {
   b.Fatal(err)
  }
  if m.B != 42 {
   b.Fatalf("unexpected value %d for B", m.B)
  }
 }
}

結果如下。

BenchmarkPopulateReflect-16   15941916    68.3 ns/op  8 B/op     1 allocs/op

這是好還是壞?好吧,內存分配可從來不是好事。你可能想知道為什么需要在堆上分配內存來將結構體字段設置為 42。但總體而言,68ns 的時間并不長。在通過網絡發出任何類型的請求時間中,你可以容納很多 68ns。

優化一:加入緩存策略

我們能做得更好嗎?好吧,通常我們運行的程序不會只做一件事然后停止。他們通常一遍又一遍地做著非常相似的事情。因此,我們可以設置一些東西以使重復的事情速度變快嗎?

如果仔細查看我們正在執行的反射檢查,我們會發現它們都取決于傳入值的類型。如果我們將類型結果緩存起來,那么對于每種類型而言,我們只會進行一次檢查。

我們再來考慮內存分配的問題。之前我們調用 Value.FieldByName 方法,實際是 Value.FieldByName 調用 Type.FieldByName,其調用 structType.FieldByName,最后調用 structType.Field 來引起內存分配的。我們可以在類型上調用 FieldByName 并緩存一些東西來獲取 B 字段的值嗎?實際上,如果我們緩存 Field.Index,就可以使用它來獲取字段值而無需分配。

新代碼版本如下

var cache = make(map[reflect.Type][]int)

func populateStructReflectCache(in interface{}) error {
 typ := reflect.TypeOf(in)

 index, ok := cache[typ]
 if !ok {
  if typ.Kind() != reflect.Ptr {
   return fmt.Errorf("you must pass in a pointer")
  }
  if typ.Elem().Kind() != reflect.Struct {
   return fmt.Errorf("you must pass in a pointer to a struct")
  }
  f, ok := typ.Elem().FieldByName("B")
  if !ok {
   return fmt.Errorf("struct does not have field B")
  }
  index = f.Index
  cache[typ] = index
 }

 val := reflect.ValueOf(in)
 elmv := val.Elem()

 fval := elmv.FieldByIndex(index)
 fval.SetInt(42)

 return nil
}

因為沒有任何內存分配,新的基準測試變得更快。

BenchmarkPopulateReflectCache-16  35881779    30.9 ns/op   0 B/op   0 allocs/op

優化二:利用字段偏移量

我們能做得更好嗎?好吧,如果我們知道結構體字段 B 的偏移量并且知道它是 int 類型,就可以將其直接寫入內存。我們可以從接口中恢復指向結構體的指針,因為空接口實際上是具有兩個指針的結構的語法糖:第一個指向有關類型的信息,第二個指向值。

type eface struct {
 _type *_type
 data  unsafe.Pointer
}

我們可以使用結構體中字段偏移量來直接尋址該值的字段 B。

新代碼如下。

var unsafeCache = make(map[reflect.Type]uintptr)

type intface struct {
 typ   unsafe.Pointer
 value unsafe.Pointer
}

func populateStructUnsafe(in interface{}) error {
 typ := reflect.TypeOf(in)

 offset, ok := unsafeCache[typ]
 if !ok {
  if typ.Kind() != reflect.Ptr {
   return fmt.Errorf("you must pass in a pointer")
  }
  if typ.Elem().Kind() != reflect.Struct {
   return fmt.Errorf("you must pass in a pointer to a struct")
  }
  f, ok := typ.Elem().FieldByName("B")
  if !ok {
   return fmt.Errorf("struct does not have field B")
  }
  if f.Type.Kind() != reflect.Int {
   return fmt.Errorf("field B should be an int")
  }
  offset = f.Offset
  unsafeCache[typ] = offset
 }

 structPtr := (*intface)(unsafe.Pointer(&in)).value
 *(*int)(unsafe.Pointer(uintptr(structPtr) + offset)) = 42

 return nil
}

新的基準測試表明這將更快。

BenchmarkPopulateUnsafe-16  62726018    19.5 ns/op     0 B/op     0 allocs/op

優化三:更改緩存 key 類型

還能讓它走得更快嗎?如果我們對 CPU 進行采樣,將會看到大部分時間都用于訪問 map,它還會顯示 map 訪問在調用 runtime.interhash 和 runtime.interequal。這些是用于 hash 接口并檢查它們是否相等的函數。也許使用更簡單的 key 會加快速度?我們可以使用來自接口的類型信息的地址,而不是 reflect.Type 本身。

var unsafeCache2 = make(map[uintptr]uintptr)

func populateStructUnsafe2(in interface{}) error {
 inf := (*intface)(unsafe.Pointer(&in))

 offset, ok := unsafeCache2[uintptr(inf.typ)]
 if !ok {
  typ := reflect.TypeOf(in)
  if typ.Kind() != reflect.Ptr {
   return fmt.Errorf("you must pass in a pointer")
  }
  if typ.Elem().Kind() != reflect.Struct {
   return fmt.Errorf("you must pass in a pointer to a struct")
  }
  f, ok := typ.Elem().FieldByName("B")
  if !ok {
   return fmt.Errorf("struct does not have field B")
  }
  if f.Type.Kind() != reflect.Int {
   return fmt.Errorf("field B should be an int")
  }
  offset = f.Offset
  unsafeCache2[uintptr(inf.typ)] = offset
 }

 *(*int)(unsafe.Pointer(uintptr(inf.value) + offset)) = 42

 return nil
}

這是新版本的基準測試結果,它又快了很多。

BenchmarkPopulateUnsafe2-16  230836136    5.16 ns/op    0 B/op     0 allocs/op

優化四:引入描述符

還能更快嗎?通常如果我們要將數據 unmarshaling 到結構體中,它總是相同的結構。因此,我們可以將功能一分為二,其中一個函數用于檢查結構是否符合要求并返回一個描述符,另外一個函數則可以在之后的填充調用中使用該描述符。

以下是我們的新代碼版本。調用者應該在初始化時調用describeType函數以獲得一個typeDescriptor,之后調用populateStructUnsafe3函數時會用到它。在這個非常簡單的例子中,typeDescriptor只是結構體中B字段的偏移量。

type typeDescriptor uintptr

func describeType(in interface{}) (typeDescriptor, error) {
 typ := reflect.TypeOf(in)
 if typ.Kind() != reflect.Ptr {
  return 0, fmt.Errorf("you must pass in a pointer")
 }
 if typ.Elem().Kind() != reflect.Struct {
  return 0, fmt.Errorf("you must pass in a pointer to a struct")
 }
 f, ok := typ.Elem().FieldByName("B")
 if !ok {
  return 0, fmt.Errorf("struct does not have field B")
 }
 if f.Type.Kind() != reflect.Int {
  return 0, fmt.Errorf("field B should be an int")
 }
 return typeDescriptor(f.Offset), nil
}

func populateStructUnsafe3(in interface{}, ti typeDescriptor) error {
 structPtr := (*intface)(unsafe.Pointer(&in)).value
 *(*int)(unsafe.Pointer(uintptr(structPtr) + uintptr(ti))) = 42
 return nil
}

以下是如何使用describeType調用的新基準測試。

func BenchmarkPopulateUnsafe3(b *testing.B) {
 b.ReportAllocs()
 var m SimpleStruct

 descriptor, err := describeType((*SimpleStruct)(nil))
 if err != nil {
  b.Fatal(err)
 }

 for i := 0; i < b.N; i++ {
  if err := populateStructUnsafe3(&m, descriptor); err != nil {
   b.Fatal(err)
  }
  if m.B != 42 {
   b.Fatalf("unexpected value %d for B", m.B)
  }
 }
}

現在基準測試結果變得相當快。

BenchmarkPopulateUnsafe3-16  1000000000     0.359 ns/op    0 B/op   0 allocs/op

這有多棒?如果我們以文章開頭原始的 populateStruct 函數編寫基準測試,可以看到在不使用反射的情況下,填充這個結構體的速度有多快。

BenchmarkPopulate-16        1000000000      0.234 ns/op    0 B/op   0 allocs/op

不出所料,這甚至比我們最好的基于反射的版本還要快一點,但它也沒有快太多。

到此,關于“怎么讓Go語言中的反射加快”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

贵南县| 新余市| 日喀则市| 温州市| 奉化市| 莱州市| 迁西县| 彝良县| 大连市| 广南县| 天镇县| 田林县| 孝义市| 徐汇区| 卢氏县| 潮安县| 腾冲县| 灵台县| 南江县| 华坪县| 公安县| 沁阳市| 贞丰县| 新营市| 东乡族自治县| 东宁县| 武平县| 舞钢市| 庆云县| 襄城县| 清新县| 舟山市| 新邵县| 喀喇沁旗| 托克逊县| 小金县| 马关县| 无棣县| 虹口区| 湛江市| 海伦市|