成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

迷惑了,Go len() 是怎么計算出來的?

開發 后端
最近看到了一個很有意思的話題,我們平時常常會用 Go 的內置函數 len 去獲取各種 map、slice 的長度,那他是怎么實現的呢?

[[416772]]

本文轉載自微信公眾號「腦子進煎魚了」,作者陳煎魚。轉載本文請聯系腦子進煎魚了公眾號。

大家好,我是煎魚。

最近看到了一個很有意思的話題,我們平時常常會用 Go 的內置函數 len 去獲取各種 map、slice 的長度,那他是怎么實現的呢?

正當我想去看看 len 的具體實現時,一展身手,卻發現竟然是個空方法:

  1. func len(v Type) int 

看注解也沒有 link 到其他 runtime 函數,那么 len 函數是如何被調用的呢?

先前也做了一些筆記,在此分享給大家,共同進步。

謎底

今天就由煎魚帶大家一同解開這個謎底。既然是謎底,那就一開始就揭開。

其實 Go 語言中并沒有 len 函數的具體實現代碼,他其實是 Go 編譯器的 "魔法" ,不是實際的函數調用。

接下來將展開這部分,我們可以更深入地了解 Go 編譯器的內部工作原理。

編譯器

在 Go 編譯器編譯時會解析命令行參數中指定的標志和 Go 源文件,對解析后的 Go 包進行類型檢查,將函數編譯為機器代碼。代碼,最后將編譯后的包定義寫到磁盤上。

內部定義基本類型、內置函數和操作函數的階段是在 types/universe.go 當中。同時會進行內置函數和具體的操作符匹配,可以明確知道內置函數 len 對應的是 OLEN:

  1. var builtinFuncs = [...]struct { 
  2.  name string 
  3.  op   Op 
  4. }{ 
  5.  {"append", OAPPEND}, 
  6.  {"cap", OCAP}, 
  7.  {"close", OCLOSE}, 
  8.  {"complex", OCOMPLEX}, 
  9.  {"copy", OCOPY}, 
  10.  {"delete", ODELETE}, 
  11.  {"imag", OIMAG}, 
  12.  {"len", OLEN}, 
  13.  ... 

在編譯時,上分為五個階段進行類型檢查:

  • 第一階段:常量、類型、以及函數的名稱和類型。
  • 第二階段:變量賦值、接口賦值、別名聲明。
  • 第三階段:類型檢查函數體。
  • 第四階段:檢查外部聲明。
  • 第五階段:檢查類型的地圖鍵,未使用的導入。

如果最后一個類型檢查階段遇到 len 函數,就會轉換為 UnaryExpr 類型,一個 UnaryExpr 節點代表一個單數表達式,也最終就是不會成為函數調用:

  1. func typecheck1(n ir.Node, top int) ir.Node { 
  2.  if n, ok := n.(*ir.Name); ok { 
  3.   typecheckdef(n) 
  4.  } 
  5.  
  6.  switch n.Op() { 
  7.  ... 
  8.  case ir.OCAP, ir.OLEN: 
  9.   n := n.(*ir.UnaryExpr) 
  10.   return tcLenCap(n) 
  11.  } 

在調用 *ir.UnaryExpr 轉換完畢后,會調用 tcLenCap,也就是 typecheck,使用 okforlen 數組來驗證參數的合法性或發出相關錯誤信息:

  1. func tcLenCap(n *ir.UnaryExpr) ir.Node { 
  2.  n.X = Expr(n.X) 
  3.  n.X = DefaultLit(n.X, nil) 
  4.  n.X = implicitstar(n.X) 
  5.  ... 
  6.  var ok bool 
  7.  if n.Op() == ir.OLEN { 
  8.   ok = okforlen[t.Kind()] 
  9.  } else { 
  10.   ok = okforcap[t.Kind()] 
  11.  } 
  12.    
  13.  ... 
  14.  n.SetType(types.Types[types.TINT]) 
  15.  return n 

經歷過上面的步驟后在對所有內容進行類型檢查后,所有函數都將排隊等待編譯:

  1. base.Timer.Start("be""compilefuncs"
  2. fcount := int64(0) 
  3. for i := 0; i < len(typecheck.Target.Decls); i++ { 
  4.  if fn, ok := typecheck.Target.Decls[i].(*ir.Func); ok { 
  5.   enqueueFunc(fn) 
  6.   fcount++ 
  7.  } 
  8. base.Timer.AddEvent(fcount, "funcs"
  9.  
  10. compileFunctions() 

在經過在 buildssa 和 genssa 之后,再深入幾層,就會將 AST 樹中的 len 表達式轉換為 SSA。接著我們就可以看到 Go 語言中的每種類型的長度是怎么獲取的。

這塊的處理對應 internal/ssagen/ssa.go 的 expr 方法,如下:

  1. case ir.OLEN, ir.OCAP: 
  2.  n := n.(*ir.UnaryExpr) 
  3.  switch { 
  4.  case n.X.Type().IsSlice(): 
  5.   op := ssa.OpSliceLen 
  6.   if n.Op() == ir.OCAP { 
  7.    op = ssa.OpSliceCap 
  8.   } 
  9.   return s.newValue1(op, types.Types[types.TINT], s.expr(n.X)) 
  10.  case n.X.Type().IsString(): // string; not reachable for OCAP 
  11.   return s.newValue1(ssa.OpStringLen, types.Types[types.TINT], s.expr(n.X)) 
  12.  case n.X.Type().IsMap(), n.X.Type().IsChan(): 
  13.   return s.referenceTypeBuiltin(n, s.expr(n.X)) 
  14.  default: // array 
  15.   return s.constInt(types.Types[types.TINT], n.X.Type().NumElem()) 
  16.  } 

若是數組(array)類型,則會調用 NumElem 方法來獲取長度值:

  1. type Array struct { 
  2.  Elem  *Type  
  3.  Bound int64  
  4.  
  5. func (t *Type) NumElem() int64 { 
  6.  t.wantEtype(TARRAY) 
  7.  return t.Extra.(*Array).Bound 

若是字典(map)類型或通道(channel),將會調用 referenceTypeBuiltin 方法:

  1. func (s *state) referenceTypeBuiltin(n *ir.UnaryExpr, x *ssa.Value) *ssa.Value { 
  2.  lenType := n.Type() 
  3.  nilValue := s.constNil(types.Types[types.TUINTPTR]) 
  4.  cmp := s.newValue2(ssa.OpEqPtr, types.Types[types.TBOOL], x, nilValue) 
  5.  b := s.endBlock() 
  6.  b.Kind = ssa.BlockIf 
  7.  b.SetControl(cmp) 
  8.  b.Likely = ssa.BranchUnlikely 
  9.  
  10.  bThen := s.f.NewBlock(ssa.BlockPlain) 
  11.  bElse := s.f.NewBlock(ssa.BlockPlain) 
  12.  bAfter := s.f.NewBlock(ssa.BlockPlain) 
  13.  ... 
  14.  switch n.Op() { 
  15.  case ir.OLEN: 
  16.   s.vars[n] = s.load(lenType, x) 
  17.  ... 
  18.  return s.variable(n, lenType) 

該函數的作用是是獲取 map 或chan 的內存地址,并以零偏移量引用其結構布局,就像 unsafe.Pointer(uintptr(unsafe.Pointer(s)) 一樣,返回第一個字面字段的值。

那為什么要獲取結構體的第一個字段的值呢,應該是和 map 和 chan 的基礎數據結構有關:

  1. type hmap struct { 
  2.  count     int  
  3.   ... 
  4.  
  5. type hchan struct { 
  6.  qcount   uint     
  7.  ... 

是因為 map 和 chan 的基礎數據結構的第一個字段就表示長度,自然也就通過計算偏移值來獲取了。

其他的數據類型,大家可以繼續深入代碼,再細看就好了。主要還是枚舉多同類的數據類型,接著調用相應的方法。

總結

每次我們看到內置函數時,總會下意識的以為是在 runtime 內實現的??床坏?runtime 內的實現方法,又會以為是通過注解 link 的方式來解決的。

 

但需要注意,其實還有像 len 內置函數這種直接編譯器轉換的,這也是一種不錯的優化方式。

 

責任編輯:武曉燕 來源: 腦子進煎魚了
相關推薦

2022-02-24 07:56:27

Linux系統ELF

2024-04-15 00:00:00

首屏優化元素

2015-06-25 10:57:15

推薦系統老婆算出來

2022-07-14 08:22:48

Computedvue3

2009-11-06 13:54:09

Visual Stud

2021-03-01 10:38:13

深度學習編程人工智能

2023-11-08 08:09:36

幾何算法解析幾何

2017-07-25 18:36:00

機器學習WOT票房

2018-12-12 11:11:20

系統可靠性可用性

2023-05-08 00:01:29

數據分析指標標簽

2024-12-26 11:49:14

2009-09-10 16:22:48

LINQ建立數據報表

2020-03-16 10:42:23

大數據IT工具

2016-10-17 16:13:32

云計算

2012-04-25 22:58:36

2011-08-24 13:32:56

CREATE TABL中文man

2022-10-12 00:07:25

加密貨幣區塊鏈比特幣

2020-03-02 15:14:53

手機計算步數

2018-10-29 11:25:38

云計算行業科技

2016-11-18 09:37:07

EC2,云計算故障賠償
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲精品一区二区网址 | 久久国产欧美日韩精品 | 国产成人免费一区二区60岁 | 紧缚调教一区二区三区视频 | 日本免费一区二区三区视频 | 伊人婷婷 | 天天夜碰日日摸日日澡 | 天天影视色综合 | 国产日韩精品在线 | 狠狠久| 本道综合精品 | 欧美黄色小视频 | 日本欧美国产在线观看 | 99精品99| 午夜精品一区二区三区在线视频 | 一区二区国产在线观看 | 四虎影音 | 91在线精品一区二区 | 欧美精品在线一区 | 亚洲日本国产 | 亚洲精品视频在线 | 中文字幕在线免费视频 | 久久三区 | 欧美黄色一区 | 亚洲一区二区在线 | 国产伦精品一区二区三区精品视频 | 欧美中文字幕一区二区三区亚洲 | 国产精品色 | 伦理午夜电影免费观看 | 草b视频 | 精品视频久久久 | 在线中文字幕av | www.97zyz.com| 免费看黄视频网站 | 一区二区三区国产好 | 成人在线看片 | 一区二区三区四区不卡视频 | 婷婷开心激情综合五月天 | 欧美在线一区二区三区四区 | 密室大逃脱第六季大神版在线观看 | 粉嫩一区二区三区四区公司1 |