最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

讓Go反射變快的方法實例探究

 更新時間:2024年01月18日 09:50:51   作者:小白debug  
反射允許你在運(yùn)行時獲得有關(guān) Go 類型的信息,如果你曾經(jīng)愚蠢地嘗試編寫 json.Unmarshal 之類的新版本,本文將探討的就是如何使用反射來填充結(jié)構(gòu)體值

引言

最近讀到一篇關(guān)于 Go 反射的文章,作者通過反射給結(jié)構(gòu)體填充字段值的案例,充分利用 Go 的各種內(nèi)在機(jī)理,逐步探討讓代碼運(yùn)行得更快的姿勢。

文章(原文地址:https://philpearl.github.io/post/aintnecessarilyslow/ )非常有學(xué)習(xí)價值,故翻譯整理了下來。

不要使用反射,除非你真的需要。但是當(dāng)你不使用反射時,不要認(rèn)為這是因為反射很慢,它也可以很快。

反射允許你在運(yùn)行時獲得有關(guān) Go 類型的信息。如果你曾經(jīng)愚蠢地嘗試編寫 json.Unmarshal 之類的新版本,本文將探討的就是如何使用反射來填充結(jié)構(gòu)體值。

切入點(diǎn)案例

我們以一個簡單的案例為切入點(diǎn),定義一個結(jié)構(gòu)體 SimpleStruct,它包括兩個 int 類型字段 A 和 B。

type SimpleStruct struct {
    A int
    B int
}

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

在下文,我們將編寫一些函數(shù)來實現(xiàn)這一點(diǎn),它們都會將 B 設(shè)置為 42。

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

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

反射基本版

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

在 Go 中,這通常意味著需要采用 interface{} (空接口)參數(shù)。然后我們可以使用 reflect 包檢查通過空接口參數(shù)傳入的值,檢查它是否是指向結(jié)構(gòu)體的指針,找到字段 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
}

讓我們通過基準(zhǔn)測試看看它有多快。

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)
  }
 }
}

結(jié)果如下。

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

這是好還是壞?好吧,內(nèi)存分配可從來不是好事。你可能想知道為什么需要在堆上分配內(nèi)存來將結(jié)構(gòu)體字段設(shè)置為 42(可以看這個 issue:https://github.com/golang/go/issues/2320)。但總體而言,68ns 的時間并不長。在通過網(wǎng)絡(luò)發(fā)出任何類型的請求時間中,你可以容納很多 68ns。

優(yōu)化一:加入緩存策略

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

如果仔細(xì)查看我們正在執(zhí)行的反射檢查,我們會發(fā)現(xiàn)它們都取決于傳入值的類型。如果我們將類型結(jié)果緩存起來,那么對于每種類型而言,我們只會進(jìn)行一次檢查。

我們再來考慮內(nèi)存分配的問題。之前我們調(diào)用 Value.FieldByName 方法,實際是 Value.FieldByName 調(diào)用 Type.FieldByName,其調(diào)用 structType.FieldByName,最后調(diào)用 structType.Field 來引起內(nèi)存分配的。我們可以在類型上調(diào)用 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
}

因為沒有任何內(nèi)存分配,新的基準(zhǔn)測試變得更快。

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

優(yōu)化二:利用字段偏移量

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

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

我們可以使用結(jié)構(gòu)體中字段偏移量來直接尋址該值的字段 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
}

新的基準(zhǔn)測試表明這將更快。

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

優(yōu)化三:更改緩存 key 類型

還能讓它走得更快嗎?如果我們對 CPU 進(jìn)行采樣,將會看到大部分時間都用于訪問 map,它還會顯示 map 訪問在調(diào)用 runtime.interhash 和 runtime.interequal。這些是用于 hash 接口并檢查它們是否相等的函數(shù)。也許使用更簡單的 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
}

這是新版本的基準(zhǔn)測試結(jié)果,它又快了很多。

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

優(yōu)化四:引入描述符

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

以下是我們的新代碼版本。調(diào)用者應(yīng)該在初始化時調(diào)用describeType函數(shù)以獲得一個typeDescriptor,之后調(diào)用populateStructUnsafe3函數(shù)時會用到它。在這個非常簡單的例子中,typeDescriptor只是結(jié)構(gòu)體中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調(diào)用的新基準(zhǔn)測試。

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)
  }
 }
}

現(xiàn)在基準(zhǔn)測試結(jié)果變得相當(dāng)快。

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

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

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

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

總結(jié)

反射并不一定很慢,但是你必須付出相當(dāng)大的努力,通過運(yùn)用 Go 內(nèi)部機(jī)理知識,在你的代碼中隨意撒上不安全的味道 ,以使其真正加速。

最后,如果你對這種方法的實際使用感興趣,可以參考 jsoniter 庫:https://github.com/json-iterator/go,它使用 reflect2 庫:https://github.com/modern-go/reflect2 來實現(xiàn)了非常相似的方法

以上就是讓Go反射變快的方法實例探究的詳細(xì)內(nèi)容,更多關(guān)于Go反射變快的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 基于golang的輕量級工作流框架Fastflow

    基于golang的輕量級工作流框架Fastflow

    這篇文章主要介紹了基于golang的輕量級工作流框架Fastflow,fastflow 執(zhí)行任務(wù)的過程會涉及到幾個概念:Dag, Task, Action, DagInstance,本文給大家分享完整流程,需要的朋友可以參考下
    2022-05-05
  • 一文詳解Golang內(nèi)存管理之??臻g管理

    一文詳解Golang內(nèi)存管理之棧空間管理

    這篇文章主要介紹了Golang內(nèi)存管理的??臻g管理,文章通過代碼示例介紹的非常詳細(xì),對我們學(xué)習(xí)Golang內(nèi)存管理有一定的幫助,需要的朋友跟著小編一起來學(xué)習(xí)吧
    2023-06-06
  • Golang驗證器之validator是使用詳解

    Golang驗證器之validator是使用詳解

    Validator是一個?Golang?的第三方庫,用于對數(shù)據(jù)進(jìn)行校驗,常用于?API?的開發(fā)中,對客戶端發(fā)出的請求數(shù)據(jù)進(jìn)行嚴(yán)格校驗,防止惡意請求。本文通過示例詳細(xì)講解了Validator的使用,需要的可以參考一下
    2022-08-08
  • golang的空標(biāo)識符理解

    golang的空標(biāo)識符理解

    今天小編就為大家分享一篇關(guān)于golang的空標(biāo)識符理解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-04-04
  • golang生成JSON以及解析JSON

    golang生成JSON以及解析JSON

    這篇文章主要介紹了golang生成JSON以及解析JSON,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • 使用Go語言實現(xiàn)向文件寫入內(nèi)容的示例代碼

    使用Go語言實現(xiàn)向文件寫入內(nèi)容的示例代碼

    向文件寫入內(nèi)容 的完整內(nèi)容,這是文件操作的另一個核心技能,與讀取配套,適用于日志記錄、生成報告、寫配置等場景,本文通過代碼示例介紹的非常詳細(xì),需要的朋友可以參考下
    2025-07-07
  • Go語言fmt模塊的完整方法詳解及示例代碼

    Go語言fmt模塊的完整方法詳解及示例代碼

    這篇文章主要介紹了Go語言fmt模塊使用的相關(guān)資料,fmt庫最基本的功能之一就是格式化輸出,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-03-03
  • 部署Go語言項目的 N 種方法(小結(jié))

    部署Go語言項目的 N 種方法(小結(jié))

    這篇文章主要介紹了部署Go語言項目的 N 種方法(小結(jié)),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • Go語言配置解析庫viper的使用指南

    Go語言配置解析庫viper的使用指南

    viper?配置管理解析庫,是由大神?Steve?Francia?開發(fā),本文就來和大家詳細(xì)講講它的具體使用,文中的示例代碼講解詳細(xì),需要的可以收藏一下
    2023-06-06
  • Golang跳轉(zhuǎn)語句continue與goto使用語法詳解

    Golang跳轉(zhuǎn)語句continue與goto使用語法詳解

    這篇文章主要介紹了Golang跳轉(zhuǎn)語句continue與goto使用語法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2023-01-01

最新評論

慈溪市| 夏津县| 昌邑市| 华安县| 台州市| 嘉义市| 甘泉县| 沅江市| 宝坻区| 崇左市| 武汉市| 无棣县| 桦川县| 云林县| 靖宇县| 陆丰市| 九龙城区| 江华| 长宁县| 河西区| 垦利县| 西峡县| 邻水| 察雅县| 武清区| 五大连池市| 利川市| 乌拉特后旗| 兴业县| 涟源市| 余庆县| 兴山县| 宜黄县| 永康市| 娱乐| 华亭县| 太保市| 台江县| 柘荣县| 报价| 武鸣县|