欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Go語言中泛型的實現(xiàn)原理與使用

 更新時間:2022年05月06日 10:12:10   作者:kevwan  
本文是對泛型的基本思想及其在?Go?中的實現(xiàn)的一個比較容易理解的介紹,同時也是對圍繞泛型的各種性能討論的簡單總結(jié),感興趣的可以學(xué)習(xí)一下

前言

原文:A gentle introduction to generics in Go byDominik Braun

萬俊峰Kevin:我看了覺得文章非常簡單易懂,就征求了作者同意,翻譯出來給大家分享一下。

本文是對泛型的基本思想及其在 Go 中的實現(xiàn)的一個比較容易理解的介紹,同時也是對圍繞泛型的各種性能討論的簡單總結(jié)。首先,我們來看看泛型所解決的核心問題。

問題

假設(shè)我們想實現(xiàn)一個簡單的tree數(shù)據(jù)結(jié)構(gòu)。每個節(jié)點持有一個值。在 Go 1.18 之前,實現(xiàn)這種結(jié)構(gòu)的典型方法如下。

type?Node?struct?{
????value?interface{}
}

這在大多數(shù)情況下都很好用,但它也有一些缺點。

首先,interface{}可以是任何東西。如果我們想限制value可能持有的類型,例如整數(shù)和浮點數(shù),我們只能在運行時檢查這個限制。

func?(n?Node)?IsValid()?bool?{
????switch?n.value.(type)?{
????????case?int,?float32,?float64:
????????????return?true
????????default:
????????????return?false
????}
}

這樣并不可能在編譯時限制類型,像上面這樣的類型判斷在許多 Go 庫中都是很常見的做法。這里有 go-zero 項目中的例子。

第二,對 Node 中的值進行處理是非常繁瑣和容易出錯的。對值做任何事情都會涉及到某種類型的斷言,即使你可以安全地假設(shè)值持有一個int值。

number,?ok?:=?node.value.(int)
if?!ok?{
????//?...
}

double?:=?number?*?2

這些只是使用interface{}的一些不便之處,它沒有提供類型安全,并有可能導(dǎo)致難以恢復(fù)的運行時錯誤。

解決方法

我們不打算接受任意數(shù)據(jù)類型或具體類型,而是定義一個叫做T的占位符類型作為值的類型。請注意,這段代碼還不會通過編譯。

type?Node[T]?struct?{
????value?T
}

首先需要聲明泛型類型T,這是在結(jié)構(gòu)或函數(shù)名稱后面方括號里面使用的。

T可以是任何類型,只有在實例化一個具有明確類型的Node時,T才會被推導(dǎo)為該類型。

n?:=?Node[int]{
????value:?5,
}

泛型Node被實例化為Node[int](整數(shù)節(jié)點),所以T是一個int。

類型約束

上面的實現(xiàn)里,T的聲明缺少一個必要的信息:類型約束。

類型約束用于進一步限制可以作為T的可能類型。Go 本身提供了一些預(yù)定義的類型約束,但也可以使用自定義的類型約束。

type?Node[T?any]?struct?{
????value?T
}

任意類型(any)約束允許T實際上是任何類型。如果節(jié)點值需要進行比較,有一個comparable類型約束,滿足這個預(yù)定義約束的類型可以使用==進行比較。

type?Node[T?comparable]?struct?{
????value?T
}

任何類型都可以作為一個類型約束。Go 1.18 引入了一種新的interface語法,可以嵌入其他數(shù)據(jù)類型。

type?Numeric?interface?{
????int?|?float32?|?float64
}

這意味著一個接口不僅可以定義一組方法,還可以定義一組類型。使用Numeric接口作為類型約束,意味著值可以是整數(shù)或浮點數(shù)。

type?Node[T?Numeric]?struct?{
????value?T
}

重獲類型安全

相對于使用interface{},泛型類型參數(shù)的巨大優(yōu)勢在于,T的最終類型在編譯時就會被推導(dǎo)出來。為T定義一個類型約束,完全消除了運行時檢查。如果用作T的類型不滿足類型約束,代碼就不會編譯通過。

在編寫泛型代碼時,你可以像已經(jīng)知道T的最終類型一樣寫代碼。

func?(n?Node[T])?Value()?T?{
????return?n.value
}

上面的函數(shù)返回n.Value,它的類型是T。因此,返回值是T,如果T是一個整數(shù),那么返回類型就已知是int。因此,返回值可以直接作為一個整數(shù)使用,不需要任何類型斷言。

n?:=?Node[int]{
????value:?5,
}

double?:=?n.Value()?*?2

在編譯時恢復(fù)類型安全使 Go 代碼更可靠,更不容易出錯。

泛型使用場景

Ian Lance Taylor的 When To Use Generics 中列出了泛型的典型使用場景,歸結(jié)為三種主要情況:

  • 使用內(nèi)置的容器類型,如slicesmapschannels
  • 實現(xiàn)通用的數(shù)據(jù)結(jié)構(gòu),如linked listtree
  • 編寫一個函數(shù),其實現(xiàn)對許多類型來說都是一樣的,比如一個排序函數(shù)

一般來說,當(dāng)你不想對你所操作的值的內(nèi)容做出假設(shè)時,可以考慮使用泛型。我們例子中的Node并不太關(guān)心它持有的值。

當(dāng)不同的類型有不同的實現(xiàn)時,泛型就不是一個好的選擇。另外,不要把Read(r io.Reader)這樣的接口函數(shù)簽名改為Read[T io.Reader](r T)這樣的通用簽名。

性能

要了解泛型的性能及其在 Go 中的實現(xiàn),首先需要了解一般情況下實現(xiàn)泛型的兩種最常見方式。

這是對各種性能的深入研究和圍繞它們進行的討論的簡要介紹。你大概率不太需要關(guān)心 Go 中泛型的性能。

虛擬方法表

在編譯器中實現(xiàn)泛型的一種方法是使用Virtual Method Table。泛型函數(shù)被修改成只接受指針作為參數(shù)的方式。然后,這些值被分配到堆上,這些值的指針被傳遞給泛型函數(shù)。這樣做是因為指針看起來總是一樣的,不管它指向的是什么類型。

如果這些值是對象,而泛型函數(shù)需要調(diào)用這些對象的方法,它就不能再這樣做了。該函數(shù)只有一個指向?qū)ο蟮闹羔槪恢浪鼈兊姆椒ㄔ谀睦?。因此,它需要一個可以查詢方法的內(nèi)存地址的表格:Virtual Method Table。這種所謂的動態(tài)調(diào)度已經(jīng)被 Go 和 Java 等語言中的接口所使用。

Virtual Method Table不僅可以用來實現(xiàn)泛型,還可以用來實現(xiàn)其他類型的多態(tài)性。然而,推導(dǎo)這些指針和調(diào)用虛擬函數(shù)要比直接調(diào)用函數(shù)慢,而且使用Virtual Method Table會阻止編譯器進行優(yōu)化。

單態(tài)化

一個更簡單的方法是單態(tài)化(Monomorphization),編譯器為每個被調(diào)用的數(shù)據(jù)類型生成一個泛型函數(shù)的副本。

func?max[T?Numeric](a,?b?T)?T?{
????//?...
}

larger?:=?max(3,?5)

由于上面顯示的max函數(shù)是用兩個整數(shù)調(diào)用的,編譯器在對代碼進行單態(tài)化時將為int生成一個max的副本。

func?maxInt(a,?b?int)?int?{
????//?...
}

larger?:=?maxInt(3,?5)

最大的優(yōu)勢是,Monomorphization帶來的運行時性能明顯好于使用Virtual Method Table。直接方法調(diào)用不僅更有效率,而且還能適用整個編譯器的優(yōu)化鏈。不過,這樣做的代價是編譯時長,為所有相關(guān)類型生成泛型函數(shù)的副本是非常耗時的。

Go 的實現(xiàn)

這兩種方法中哪一種最適合 Go?快速編譯很重要,但運行時性能也很重要。為了滿足這些要求,Go 團隊決定在實現(xiàn)泛型時混合兩種方法。

Go 使用Monomorphization,但試圖減少需要生成的函數(shù)副本的數(shù)量。它不是為每個類型創(chuàng)建一個副本,而是為內(nèi)存中的每個布局生成一個副本:int、float64、Node和其他所謂的"值類型"在內(nèi)存中看起來都不一樣,因此泛型函數(shù)將為所有這些類型復(fù)制副本。

與值類型相反,指針和接口在內(nèi)存中總是有相同的布局。編譯器將為指針和接口的調(diào)用生成一個泛型函數(shù)的副本。就像Virtual Method Table一樣,泛型函數(shù)接收指針,因此需要一個表來動態(tài)地查找方法地址。在 Go 實現(xiàn)中的字典與虛擬方法表的性能特點相同。

結(jié)論

這種混合方法的好處是,你在使用值類型的調(diào)用中獲得了Monomorphization的性能優(yōu)勢,而只在使用指針或接口的調(diào)用中付出了Virtual Method Table的成本。

在性能討論中經(jīng)常被忽略的是,所有這些好處和成本只涉及到函數(shù)的調(diào)用。通常情況下,大部分的執(zhí)行時間是在函數(shù)內(nèi)部使用的。調(diào)用方法的性能開銷可能不會成為性能瓶頸,即使是這樣,也要考慮先優(yōu)化函數(shù)實現(xiàn),再考慮調(diào)用開銷。

到此這篇關(guān)于詳解Go語言中泛型的實現(xiàn)原理與使用的文章就介紹到這了,更多相關(guān)Go語言泛型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 淺談Golang內(nèi)存逃逸

    淺談Golang內(nèi)存逃逸

    本文主要介紹了Golang內(nèi)存逃逸,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • Go語言中實現(xiàn)多線程定時任務(wù)的示例代碼

    Go語言中實現(xiàn)多線程定時任務(wù)的示例代碼

    本文主要介紹了Go語言中實現(xiàn)多線程定時任務(wù)的示例代碼,使用goroutine和channel實現(xiàn)輕量級線程及通信,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-09-09
  • Golang使用DuckDB查詢Parquet文件數(shù)據(jù)的操作代碼

    Golang使用DuckDB查詢Parquet文件數(shù)據(jù)的操作代碼

    本文介紹DuckDB查詢Parquet文件的典型應(yīng)用場景,掌握DuckDB會讓你的產(chǎn)品分析能力更強,相反系統(tǒng)運營成本相對較低,為了示例完整,我也提供了如何使用Python導(dǎo)出MongoDB數(shù)據(jù),需要的朋友可以參考下
    2025-01-01
  • Golang信號量設(shè)計實現(xiàn)示例詳解

    Golang信號量設(shè)計實現(xiàn)示例詳解

    這篇文章主要為大家介紹了Golang信號量設(shè)計實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • Go語言數(shù)據(jù)類型詳細介紹

    Go語言數(shù)據(jù)類型詳細介紹

    這篇文章主要介紹了Go語言數(shù)據(jù)類型詳細介紹,Go語言數(shù)據(jù)類型包含基礎(chǔ)類型和復(fù)合類型兩大類,下文關(guān)于這兩類型的相關(guān)介紹,需要的小伙伴可以參考一下
    2022-03-03
  • Go基礎(chǔ)系列:Go切片(分片)slice詳解

    Go基礎(chǔ)系列:Go切片(分片)slice詳解

    這篇文章主要介紹了Go語言中的切片(分片)slice詳細說明?,需要的朋友可以參考下
    2022-04-04
  • 使用GORM將PostgreSQL集成到Go框架中

    使用GORM將PostgreSQL集成到Go框架中

    在go中集成postgresql需使用gorm orm,步驟如下:安裝go和postgresql,安裝 gorm:go get -u gorm.io/gorm,配置數(shù)據(jù)庫連接字符串,定義模型類,遷移數(shù)據(jù)庫架構(gòu),使用 gorm 進行增刪改查操作,本指南將介紹如何使用 GORM(一個廣受歡迎的 ORM),將PostgreSQL集成到你的Go應(yīng)用中
    2024-08-08
  • Go語言聲明一個多行字符串的變量

    Go語言聲明一個多行字符串的變量

    這篇文章主要介紹了Go語言聲明一個多行字符串的變量的方法和示例,非常簡單實用,有需要的小伙伴可以參考下。
    2015-04-04
  • go語言寫的簡要數(shù)據(jù)同步工具詳解

    go語言寫的簡要數(shù)據(jù)同步工具詳解

    作為go-etl工具的作者,想要安利一下這個小巧的數(shù)據(jù)同步工具,它在同步百萬級別的數(shù)據(jù)時表現(xiàn)極為優(yōu)異,基本能在幾分鐘完成數(shù)據(jù)同步,這篇文章主要介紹了go語言寫的簡要數(shù)據(jù)同步工具,需要的朋友可以參考下
    2024-07-07
  • go語法入門匿名函數(shù)定義及使用示例詳解

    go語法入門匿名函數(shù)定義及使用示例詳解

    這篇文章主要為大家介紹了go語法入門匿名函數(shù)定義及使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09

最新評論