golang源碼中文,golang 源碼

本文目錄一覽:

golang map源碼淺析

golang 中 map的實現結構為: 哈希表 + 鏈表。 其中鏈表,作用是當發生hash衝突時,拉鏈法生成的結點。

可以看到, []bmap 是一個hash table, 每一個 bmap是我們常說的「桶」。 經過hash 函數計算出來相同的hash值, 放到相同的桶中。 一個 bmap中可以存放 8個 元素, 如果多出8個,則生成新的結點,尾接到隊尾。

以上是只是靜態文件 src/runtime/map.go 中的定義。 實際上編譯期間會給它加料 ,動態地創建一個新的結構:

上圖就是 bmap的內存模型, HOB Hash 指的就是 top hash。 注意到 key 和 value 是各自放在一起的,並不是 key/value/key/value/… 這樣的形式。源碼里說明這樣的好處是在某些情況下可以省略掉 padding 字段,節省內存空間。

每個 bmap設計成 最多只能放 8 個 key-value 對 ,如果有第 9 個 key-value 落入當前的 bmap,那就需要再構建一個 bmap,通過 overflow 指針連接起來。

map創建方法:

我們實際上是通過調用的 makemap ,來創建map的。實際工作只是初始化了hmap中的各種字段,如:設置B的大小, 設置hash 種子 hash 0.

注意 :

makemap 返回是*hmap 指針, 即 map 是引用對象, 對map的操作會影響到結構體內部 。

使用方式

對應的是下面兩種方法

map的key的類型,實現了自己的hash 方式。每種類型實現hash函數方式不一樣。

key 經過哈希計算後得到hash值,共 64 個 bit 位。 其中後B 個bit位置, 用來定位當前元素落在哪一個桶里, 高8個bit 為當前 hash 值的top hash。 實際上定位key的過程是一個雙重循環的過程, 外層循環遍歷 所有的overflow, 內層循環遍歷 當前bmap 中的 8個元素 。

舉例說明: 如果當前 B 的值為 5, 那麼buckets 的長度 為 2^5 = 32。假設有個key 經過hash函數計算後,得到的hash結果為:

外層遍歷bucket 中的鏈表

內層循環遍歷 bmap中的8個 cell

建議先不看此部分內容,看完後續 修改 map中元素 – 擴容 操作後 再回頭看此部分內容。

擴容前的數據:

等量擴容後的數據:

等量擴容後,查找方式和原本相同, 不多做贅述。

兩倍擴容後的數據

兩倍擴容後,oldbuckets 的元素,可能被分配成了兩部分。查找順序如下:

此處只分析 mapaccess1 ,。 mapaccess2 相比 mapaccess1 多添加了是否找到的bool值, 有興趣可自行看一下。

使用方式:

步驟如下:

擴容條件 :

擴容的標識 : h.oldbuckets != nil

假設當前定位到了新的buckets的3號桶中,首先會判斷oldbuckets中的對應的桶有沒有被搬遷過。 如果搬遷過了,不需要看原來的桶了,直接遍歷新的buckets的3號桶。

擴容前:

等量擴容結果

雙倍擴容會將old buckets上的元素分配到x, y兩個部key 1 B == 0 分配到x部分,key 1 B == 1 分配到y部分

注意: 當前只對雙倍擴容描述, 等量擴容只是重新填充了一下元素, 相對位置沒有改變。

假設當前map 的B == 5,原本元素經過hash函數計算的 hash 值為:

因為雙倍擴容之後 B = B + 1,此時B == 6。key 1 B == 1, 即 當前元素rehash到高位,新buckets中 y 部分. 否則 key 1 B == 0 則rehash到低位,即x 部分。

使用方式:

可以看到,每一遍歷生成迭代器的時候,會隨機選取一個bucket 以及 一個cell開始。 從前往後遍歷,再次遍歷到起始位置時,遍歷完成。

Golang database/sql源碼分析

Gorm是Go語言開發用的比較多的一個ORM。它的功能比較全:

但是這篇文章中並不會直接看Gorm的源碼,我們會先從database/sql分析。原因是Gorm也是基於這個包來封裝的一些功能。所以只有先了解了database/sql包才能更加好的理解Gorm源碼。

database/sql 其實也是一個對於mysql驅動的上層封裝。」github.com/go-sql-driver/mysql」就是一個對於mysql的驅動,database/sql 就是在這個基礎上做的基本封裝包含連接池的使用

下面這個是最基本的增刪改查操作

操作分下面幾個步驟:

因為Gorm的連接池就是使用database/sql包中的連接池,所以這裡我們需要學習一下包里的連接池的源碼實現。其實所有連接池最重要的就是連接池對象、獲取函數、釋放函數下面來看一下database/sql中的連接池。

DB對象

獲取方法

釋放連接方法

連接池的實現有很多方法,在database/sql包中使用的是chan阻塞 使用map記錄等待列表,等到有連接釋放的時候再把連接傳入等待列表中的chan 不在阻塞返回連接。

之前我們看到的Redigo是使用一個chan 來阻塞,然後釋放的時候放入空閑列表,在往這一個chan中傳入struct{}{},讓程序繼續 獲取的時候再從空閑列表中獲取。並且使用的是鏈表的結構來存儲空閑列表。

database/sql 是對於mysql驅動的封裝,然而Gorm則是對於database/sql的再次封裝。讓我們可以更加簡單的實現對於mysql數據庫的操作。

Golang實驗性功能SetMaxHeap 固定值GC

簡單來說, SetMaxHeap 提供了一種可以設置固定觸發閾值的 GC (Garbage Collection垃圾回收)方式

官方源碼鏈接

大量臨時對象分配導致的 GC 觸發頻率過高, GC 後實際存活的對象較少,

或者機器內存較充足,希望使用剩餘內存,降低 GC 頻率的場景

GC 會 STW ( Stop The World ),對於時延敏感場景,在一個周期內連續觸發兩輪 GC ,那麼 STW 和 GC 佔用的 CPU 資源都會造成很大的影響, SetMaxHeap 並不一定是完美的,在某些場景下做了些權衡,官方也在進行相關的實驗,當前方案仍沒有合入主版本。

先看下如果沒有 SetMaxHeap ,對於如上所述的場景的解決方案

這裡簡單說下 GC 的幾個值的含義,可通過 GODEBUG=gctrace=1 獲得如下數據

這裡只關注 128-132-67 MB 135 MB goal ,

分別為 GC開始時內存使用量 – GC標記完成時內存使用量 – GC標記完成時的存活內存量 本輪GC標記完成時的 預期 內存使用量(上一輪 GC 完成時確定)

引用 GC peace設計文檔 中的一張圖來說明

對應關係如下:

簡單說下 GC pacing (信用機制)

GC pacing 有兩個目標,

那麼當一輪 GC 完成時,如何只根據本輪 GC 存活量去實現這兩個小目標呢?

這裡實際是根據當前的一些數據或狀態去 預估 「未來」,所有會存在些誤差

首先確定 gc Goal goal = memstats.heap_marked + memstats.heap_marked*uint64(gcpercent)/100

heap_marked 為本輪 GC 存活量, gcpercent 默認為 100 ,可以通過環境變量 GOGC=100 或者 debug.SetGCPercent(100) 來設置

那麼默認情況下 goal = 2 * heap_marked

gc_trigger 是與 goal 相關的一個值( gc_trigger 大約為 goal 的 90% 左右),每輪 GC 標記完成時,會根據 |Ha-Hg| 和實際使用的 cpu 資源 動態調整 gc_trigger 與 goal 的差值

goal 與 gc_trigger 的差值即為,為 GC 期間分配的對象所預留的空間

GC pacing 還會預估下一輪 GC 發生時,需要掃描對象對象的總量,進而換算為下一輪 GC 所需的工作量,進而計算出 mark assist 的值

本輪 GC 觸發( gc_trigger ),到本輪的 goal 期間,需要儘力完成 GC mark 標記操作,所以當 GC 期間,某個 goroutine 分配大量內存時,就會被拉去做 mark assist 工作,先進行 GC mark 標記賺取足夠的信用值後,才能分配對應大小的對象

根據本輪 GC 存活的內存量( heap_marked )和下一輪 GC 觸發的閾值( gc_trigger )計算 sweep assist 的值,本輪 GC 完成,到下一輪 GC 觸發( gc_trigger )時,需要儘力完成 sweep 清掃操作

預估下一輪 GC 所需的工作量的方式如下:

繼續分析文章開頭的問題,如何充分利用剩餘內存,降低 GC 頻率和 GC 對 CPU 的資源消耗

如上圖可以看出, GC 後,存活的對象為 2GB 左右,如果將 gcpercent 設置為 400 ,那麼就可以將下一輪 GC 觸發閾值提升到 10GB 左右

前面一輪看起來很好,提升了 GC 觸發的閾值到 10GB ,但是如果某一輪 GC 後的存活對象到達 2.5GB 的時候,那麼下一輪 GC 觸發的閾值,將會超過內存閾值,造成 OOM ( Out of Memory ),進而導致程序崩潰。

可以通過 GOGC=off 或者 debug.SetGCPercent(-1) 來關閉 GC

可以通過進程外監控內存使用狀態,使用信號觸發的方式通知程序,或 ReadMemStats 、或 linkname runtime.heapRetained 等方式進行堆內存使用的監測

可以通過調用 runtime.GC() 或者 debug.FreeOSMemory() 來手動進行 GC 。

這裡還需要說幾個事情來解釋這個方案所存在的問題

通過 GOGC=off 或者 debug.SetGCPercent(-1) 是如何關閉 GC 的?

gc 4 @1.006s 0%: 0.033+5.6+0.024 ms clock, 0.27+4.4/11/25+0.19 ms cpu, 428-428-16 MB, 17592186044415 MB goal, 8 P (forced)

通過 GC trace 可以看出,上面所說的 goal 變成了一個很詭異的值 17592186044415

實際上關閉 GC 後, Go 會將 goal 設置為一個極大值 ^uint64(0) ,那麼對應的 GC 觸發閾值也被調成了一個極大值,這種處理方式看起來也沒什麼問題,將閾值調大,預期永遠不會再觸發 GC

那麼如果在關閉 GC 的情況下,手動調用 runtime.GC() 會導致什麼呢?

由於 goal 和 gc_trigger 被設置成了極大值, mark assist 和 sweep assist 也會按照這個錯誤的值去計算,導致工作量預估錯誤,這一點可以從 trace 中進行證明

可以看到很詭異的 trace 圖,這裡不做深究,該方案與 GC pacing 信用機制不兼容

記住,不要在關閉 GC 的情況下手動觸發 GC ,至少在當前 Go1.14 版本中仍存在這個問題

SetMaxHeap 的實現原理,簡單來說是強行控制了 goal 的值

註: SetMaxHeap ,本質上是一個軟限制,並不能解決 極端場景 下的 OOM ,可以配合內存監控和 debug.FreeOSMemory() 使用

SetMaxHeap 控制的是堆內存大小, Go 中除了堆內存還分配了如下內存,所以實際使用過程中,與實際硬件內存閾值之間需要留有一部分餘量。

對於文章開始所述問題,使用 SetMaxHeap 後,預期的 GC 過程大概是這個樣子

簡單用法1

該方法簡單粗暴,直接將 goal 設置為了固定值

註:通過上文所講,觸發 GC 實際上是 gc_trigger ,所以當閾值設置為 12GB 時,會提前一點觸發 GC ,這裡為了描述方便,近似認為 gc_trigger=goal

簡單用法2

當不關閉 GC 時, SetMaxHeap 的邏輯是, goal 仍按照 gcpercent 進行計算,當 goal 小於 SetMaxHeap 閾值時不進行處理;當 goal 大於 SetMaxHeap 閾值時,將 goal 限制為 SetMaxHeap 閾值

註:通過上文所講,觸發 GC 實際上是 gc_trigger ,所以當閾值設置為 12GB 時,會提前一點觸發 GC ,這裡為了描述方便,近似認為 gc_trigger=goal

切換到 go1.14 分支,作者選擇了 git checkout go1.14.5

選擇官方提供的 cherry-pick 方式(可能需要梯子,文件改動不多,我後面會列出具體改動)

git fetch “” refs/changes/67/227767/3 git cherry-pick FETCH_HEAD

需要重新編譯Go源碼

注意點:

下面源碼中的官方注釋說的比較清楚,在一些關鍵位置加入了中文注釋

入參bytes為要設置的閾值

notify 簡單理解為 GC 的策略 發生變化時會向 channel 發送通知,後續源碼可以看出「策略」具體指哪些內容

返回值為本次設置之前的 MaxHeap 值

$GOROOT/src/runtime/debug/garbage.go

$GOROOT/src/runtime/mgc.go

註:作者盡量用通俗易懂的語言去解釋 Go 的一些機制和 SetMaxHeap 功能,可能有些描述與實現細節不完全一致,如有錯誤還請指出

golang性能測試框架k6源碼分析

k6是新興的性能測試框架,比肩jmeter,另外測試腳本使用js,更加適合自動化的架構。

k6啟動的框架是使用golang的cli標準框架cobra,入口函數

進入cobra框架後,我們直接查看getRunCmd,這個是命令run的入口,主要工作都是從這裡開始。

重點關注初始化Runner,這個是通過js腳本,使用goja庫解析後,生成的實際執行單元。

進入js目錄,查看Runner的結構,runner.go

Runner有一些配置屬性,另外還有方法,方法用lib.Runner的接口進行規範。

Runner有一個NewVU方法,裏面定義了連接參數,實現api測試

返回主函數,在初始化完成Runner後,啟動調度器,以及做結果收集

最終封裝成一個engine

啟動測試

golang 把中文轉換為首字母的方法

Go語言的string模塊包含了ToLower和ToUpper函數,用於將字符串轉換成小寫和大寫

代碼如下:

package main

import (

“fmt”

“strings”

)

func main() {

fmt.Println(strings.ToUpper(“hello world”))

}

golang unicode/utf8源碼分析

包 utf-8 實現的功能和常量用於文章utf8編碼,包含runes和utf8位元組序列的轉換功能.在unicode中,一個中文佔兩個位元組,utf-8中一個中文佔三個位元組,golang默認的編碼是utf-8編碼,因此默認一個中文佔三個位元組,但是golang中的字符串底層實際上是一個byte數組.

Output:

RuneSelf該值的位元組碼值為128,在判斷是否是常規的ascii碼是使用。hicb位元組碼值為191. FF 的對應的位元組碼為255。

計算字符串中的rune數量,原理:首先取出字符串的碼值,然後判斷是不是個小於128的,如果是小於則直接continue.rune個數++.

如果是個十六進制f1.的則是無效字符,直接continue.rune個數++,也就是說一個無效的字符也當成一個字長為1的rune.如果字符的碼值在first列表中的值和7按位的結果為其字長,比如上面示例中的 鋼 。其字長為三位,第一位的值為 233 .二進制形式為 11101001 ;與7按位與後的值為0.從acceptRanges中取出的結果為{locb, hicb}。也就是標識 ox80 到 0xbf 之間的值。而結果n也就是直接size+3跳過3個位元組後,rune個數++。其他函數的處理流程差不多,不再過多敘述。

示例:

ValidString返回值表明參數字符串是否是一個合法的可utf8編碼的字符串。

RuneCount返回參數中包含的rune數量,第一個例子中將 utf8.RuneCountInString ,改成該方法調用,返回的結果相同。錯誤的和短的被當成一個長一位元組的rune.單個字符 H 就表示一個長度為1位元組的rune.

該函數標識參數是否以一個可編碼的rune開頭,上面的例子中,因為字符串是以一個ascii碼值在0-127內的字符開頭,所以在執行

first[p[0]] 時,取到的是 p[0] 是72,在first列表中,127之前的值都相同都為 0xF0 ,十進制標識為240,與7按位與後值為0,所以,直接返回 true .

和FullRune類似,只是參數為字符串形式

原創文章,作者:ZEUY,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/139235.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
ZEUY的頭像ZEUY
上一篇 2024-10-04 00:22
下一篇 2024-10-04 00:22

相關推薦

  • 雲智直聘 源碼分析

    本文將會對雲智直聘的源碼進行分析,包括前端頁面和後端代碼,幫助讀者了解其架構、技術實現以及對一些常見的問題進行解決。通過本文的閱讀,讀者將會了解到雲智直聘的特點、優勢以及不足之處,…

    編程 2025-04-29
  • 使用Golang調用Python

    在現代軟件開發中,多種編程語言的協作是相當普遍的。其中一種使用場景是Golang調用Python,這使得在使用Python庫的同時,可以利用Golang的高性能和強大並發能力。這篇…

    編程 2025-04-29
  • Python讀取中文

    Python是一種高級編程語言,被廣泛地應用於各種領域中。而處理中文數據也是其中重要的一部分。本文將介紹在Python中如何讀取中文,為大家提供指導和幫助。 一、讀取中文文件 在P…

    編程 2025-04-29
  • jQuery Datatable分頁中文

    jQuery Datatable是一個非常流行的數據表插件,它可以幫助您快速地在頁面上創建搜索、過濾、排序和分頁的數據表格。不過,它的默認設置是英文的,今天我們就來探討如何將jQu…

    編程 2025-04-29
  • Python計算中文字符個數

    本文將從多個方面對Python計算中文字符個數進行詳細的闡述,包括字符串長度計算、正則表達式統計和模塊使用方法等內容。 一、字符串長度計算 在Python中,計算字符串長度是非常容…

    編程 2025-04-29
  • Python3亂碼轉中文

    本文將詳細介紹如何轉換Python3中的亂碼為中文字符,幫助Python3開發工程師更好的處理中文字符的問題。 一、Python3中文亂碼的原因 在Python3中,中文字符使用的…

    編程 2025-04-29
  • 使用Golang創建黑色背景圖片的方法

    本文將從多個方面介紹使用Golang創建黑色背景圖片的方法。 一、安裝必要的代碼庫和工具 在開始創建黑色背景圖片之前,我們需要先安裝必要的代碼庫和工具: go get -u git…

    編程 2025-04-29
  • 從16進制轉義到中文字符

    16進制轉義是為了在不同的字符集、不同的編碼下,能夠保證特殊字符被正確的識別和渲染。本文將從多個方面對16進制轉義做詳細的闡述,讓讀者對其有更深入的了解。 一、轉義實現 在Web開…

    編程 2025-04-28
  • opendistroforelasticsearch-kibana的中文應用

    本文將介紹opendistroforelasticsearch-kibana在中文應用中的使用方法和注意事項。 一、安裝及配置 1、安裝opendistroforelasticse…

    編程 2025-04-28
  • Python網站源碼解析

    本文將從多個方面對Python網站源碼進行詳細解析,包括搭建網站、數據處理、安全性等內容。 一、搭建網站 Python是一種高級編程語言,適用於多種領域。它也可以用於搭建網站。最常…

    編程 2025-04-28

發表回復

登錄後才能評論