go語言一個字符占用空間,go語言內(nèi)存占用

Go 空結(jié)構(gòu)體 struct{} 的使用

struct是Go中的關(guān)鍵字,用于定義結(jié)構(gòu)類型。

創(chuàng)新互聯(lián)公司主要從事網(wǎng)頁設(shè)計(jì)、PC網(wǎng)站建設(shè)(電腦版網(wǎng)站建設(shè))、wap網(wǎng)站建設(shè)(手機(jī)版網(wǎng)站建設(shè))、成都響應(yīng)式網(wǎng)站建設(shè)公司、程序開發(fā)、網(wǎng)站優(yōu)化、微網(wǎng)站、微信小程序開發(fā)等,憑借多年來在互聯(lián)網(wǎng)的打拼,我們在互聯(lián)網(wǎng)網(wǎng)站建設(shè)行業(yè)積累了豐富的成都做網(wǎng)站、網(wǎng)站建設(shè)、網(wǎng)站設(shè)計(jì)、網(wǎng)絡(luò)營銷經(jīng)驗(yàn),集策劃、開發(fā)、設(shè)計(jì)、營銷、管理等多方位專業(yè)化運(yùn)作于一體。

例如:

struct {}是一個無元素的結(jié)構(gòu)體類型,通常在沒有信息存儲時使用。優(yōu)點(diǎn)是大小為0,不需要內(nèi)存來存儲struct {}類型的值。

struct {} {}是一個復(fù)合字面量,它構(gòu)造了一個struct {}類型的值,該值也是空。

go中可以使用 unsafe.Sizeof 計(jì)算出一個數(shù)據(jù)類型實(shí)例需要占用的字節(jié)數(shù)。我們驗(yàn)證一下:

也就是說空結(jié)構(gòu)體實(shí)例不占用任何內(nèi)存空間。

Go 語言標(biāo)準(zhǔn)庫沒有提供 Set 的實(shí)現(xiàn),通常使用 map 來代替。事實(shí)上,對于集合來說,只需要 map 的鍵,而不需要值。

聲明為聲明為 map[string]struct{} ,由于struct{}是空,不關(guān)心內(nèi)容,這樣map便改造為set 。

map可以通過“comma ok”機(jī)制來獲取該key是否存在,例如 _, ok := map["key"] ,如果沒有對應(yīng)的值,ok為false??梢酝ㄟ^定義成 map[string]struct{} 的形式,值不再占用內(nèi)存。其值僅有兩種狀態(tài),有或無。如果定義的是 map[string]bool ,則結(jié)果有true、false或沒有三種狀態(tài),而且即使是將值設(shè)置為 bool 類型,也會多占據(jù) 1 個字節(jié)。因此呢,將 map 作為集合(Set)使用時,可以將值類型定義為空結(jié)構(gòu)體,僅作為占位符使用即可。

基于channels發(fā)送消息有兩個重要方面:發(fā)了消息、發(fā)了什么消息。一個強(qiáng)調(diào)了通訊的發(fā)生,一個強(qiáng)調(diào)了通訊的內(nèi)容。當(dāng)我們更希望強(qiáng)調(diào)通訊發(fā)生的時刻時,我們將它稱為 消息事件 。有些消息事件并不攜帶額外的信息,它僅僅是用作兩個goroutine之間的同步,這時候我們可以用 struct{} 空結(jié)構(gòu)體作為channels元素的類型。用來通知子協(xié)程(goroutine)執(zhí)行任務(wù),或只用來控制協(xié)程并發(fā)度。

在部分場景下,結(jié)構(gòu)體只包含方法,不包含任何的字段。這時候我們就可以使用空結(jié)構(gòu)體。

其實(shí),上面的calculateInt 可以是任何類型,如 type calculateInt bool ,但是struct{}不占用任何空間,邏輯上也更合理,因此還是它最好。

Go語言的%d,%p,%v等占位符的使用

這些是死知識,把常用的記住,不常用的直接查表就行了

golang 的fmt 包實(shí)現(xiàn)了格式化I/O函數(shù),類似于C的 printf 和 scanf。

type Human struct {

Name string

}

var people = Human{Name:"zhangsan"}

golang沒有 '%u' 點(diǎn)位符,若整數(shù)為無符號類型,默認(rèn)就會被打印成無符號的。

寬度與精度的控制格式以Unicode碼點(diǎn)為單位。寬度為該數(shù)值占用區(qū)域的最小寬度;精度為小數(shù)點(diǎn)之后的位數(shù)。

操作數(shù)的類型為int時,寬度與精度都可用字符 '*' 表示。

對于 %g/%G 而言,精度為所有數(shù)字的總數(shù),例如:123.45,%.4g 會打印123.5,(而 %6.2f 會打印123.45)。

%e 和 %f 的默認(rèn)精度為6

對大多數(shù)的數(shù)值類型而言,寬度為輸出的最小字符數(shù),如果必要的話會為已格式化的形式填充空格。

而以字符串類型,精度為輸出的最大字符數(shù),如果必要的話會直接截?cái)唷?/p>

使用起來很簡單,一般配合fmt.Printf()使用,因?yàn)閒mt的Printf()是有格式的輸出,切忌使用Println(),否則將會以字符串的形式輸出。

查看原文: golang fmt格式“占位符”

Go 語言內(nèi)存管理(三):逃逸分析

Go 語言較之 C 語言一個很大的優(yōu)勢就是自帶 GC 功能,可 GC 并不是沒有代價的。寫 C 語言的時候,在一個函數(shù)內(nèi)聲明的變量,在函數(shù)退出后會自動釋放掉,因?yàn)檫@些變量分配在棧上。如果你期望變量的數(shù)據(jù)可以在函數(shù)退出后仍然能被訪問,就需要調(diào)用 malloc 方法在堆上申請內(nèi)存,如果程序不再需要這塊內(nèi)存了,再調(diào)用 free 方法釋放掉。Go 語言不需要你主動調(diào)用 malloc 來分配堆空間,編譯器會自動分析,找出需要 malloc 的變量,使用堆內(nèi)存。編譯器的這個分析過程就叫做逃逸分析。

所以你在一個函數(shù)中通過 dict := make(map[string]int) 創(chuàng)建一個 map 變量,其背后的數(shù)據(jù)是放在??臻g上還是堆空間上,是不一定的。這要看編譯器分析的結(jié)果。

可逃逸分析并不是百分百準(zhǔn)確的,它有缺陷。有的時候你會發(fā)現(xiàn)有些變量其實(shí)在??臻g上分配完全沒問題的,但編譯后程序還是把這些數(shù)據(jù)放在了堆上。如果你了解 Go 語言編譯器逃逸分析的機(jī)制,在寫代碼的時候就可以有意識地繞開這些缺陷,使你的程序更高效。

Go 語言雖然在內(nèi)存管理方面降低了編程門檻,即使你不了解堆棧也能正常開發(fā),但如果你要在性能上較真的話,還是要掌握這些基礎(chǔ)知識。

這里不對堆內(nèi)存和棧內(nèi)存的區(qū)別做太多闡述。簡單來說就是, 棧分配廉價,堆分配昂貴。 棧空間會隨著一個函數(shù)的結(jié)束自動釋放,堆空間需要時間 GC 模塊不斷地跟蹤掃描回收。如果對這兩個概念有些迷糊,建議閱讀下面 2 個文章:

這里舉一個小例子,來對比下堆棧的差別:

stack 函數(shù)中的變量 i 在函數(shù)退出會自動釋放;而 heap 函數(shù)返回的是對變量 i 的引用,也就是說 heap() 退出后,表示變量 i 還要能被訪問,它會自動被分配到堆空間上。

他們編譯出來的代碼如下:

邏輯的復(fù)雜度不言而喻,從上面的匯編中可看到, heap() 函數(shù)調(diào)用了 runtime.newobject() 方法,它會調(diào)用 mallocgc 方法從 mcache 上申請內(nèi)存,申請的內(nèi)部邏輯前面文章已經(jīng)講述過。堆內(nèi)存分配不僅分配上邏輯比??臻g分配復(fù)雜,它最致命的是會帶來很大的管理成本,Go 語言要消耗很多的計(jì)算資源對其進(jìn)行標(biāo)記回收(也就是 GC 成本)。

Go 編輯器會自動幫我們找出需要進(jìn)行動態(tài)分配的變量,它是在編譯時追蹤一個變量的生命周期,如果能確認(rèn)一個數(shù)據(jù)只在函數(shù)空間內(nèi)訪問,不會被外部使用,則使用棧空間,否則就要使用堆空間。

我們在 go build 編譯代碼時,可使用 -gcflags '-m' 參數(shù)來查看逃逸分析日志。

以上面的兩個函數(shù)為例,編譯的日志輸出是:

日志中的 i escapes to heap 表示該變量數(shù)據(jù)逃逸到了堆上。

需要使用堆空間,所以逃逸,這沒什么可爭議的。但編譯器有時會將 不需要 使用堆空間的變量,也逃逸掉。這里是容易出現(xiàn)性能問題的大坑。網(wǎng)上有很多相關(guān)文章,列舉了一些導(dǎo)致逃逸情況,其實(shí)總結(jié)起來就一句話:

多級間接賦值容易導(dǎo)致逃逸 。

這里的多級間接指的是,對某個引用類對象中的引用類成員進(jìn)行賦值。Go 語言中的引用類數(shù)據(jù)類型有 func , interface , slice , map , chan , *Type(指針) 。

記住公式 Data.Field = Value ,如果 Data , Field 都是引用類的數(shù)據(jù)類型,則會導(dǎo)致 Value 逃逸。這里的等號 = 不單單只賦值,也表示參數(shù)傳遞。

根據(jù)公式,我們假設(shè)一個變量 data 是以下幾種類型,相應(yīng)的可以得出結(jié)論:

下面給出一些實(shí)際的例子:

如果變量值是一個函數(shù),函數(shù)的參數(shù)又是引用類型,則傳遞給它的參數(shù)都會逃逸。

上例中 te 的類型是 func(*int) ,屬于引用類型,參數(shù) *int 也是引用類型,則調(diào)用 te(j) 形成了為 te 的參數(shù)(成員) *int 賦值的現(xiàn)象,即 te.i = j 會導(dǎo)致逃逸。代碼中其他幾種調(diào)用都沒有形成 多級間接賦值 情況。

同理,如果函數(shù)的參數(shù)類型是 slice , map 或 interface{} 都會導(dǎo)致參數(shù)逃逸。

匿名函數(shù)的調(diào)用也是一樣的,它本質(zhì)上也是一個函數(shù)變量。有興趣的可以自己測試一下。

只要使用了 Interface 類型(不是 interafce{} ),那么賦值給它的變量一定會逃逸。因?yàn)? interfaceVariable.Method() 先是間接的定位到它的實(shí)際值,再調(diào)用實(shí)際值的同名方法,執(zhí)行時實(shí)際值作為參數(shù)傳遞給方法。相當(dāng)于 interfaceVariable.Method.this = realValue

向 channel 中發(fā)送數(shù)據(jù),本質(zhì)上就是為 channel 內(nèi)部的成員賦值,就像給一個 slice 中的某一項(xiàng)賦值一樣。所以 chan *Type , chan map[Type]Type , chan []Type , chan interface{} 類型都會導(dǎo)致發(fā)送到 channel 中的數(shù)據(jù)逃逸。

這本來也是情理之中的,發(fā)送給 channel 的數(shù)據(jù)是要與其他函數(shù)分享的,為了保證發(fā)送過去的指針依然可用,只能使用堆分配。

可變參數(shù)如 func(arg ...string) 實(shí)際與 func(arg []string) 是一樣的,會增加一層訪問路徑。這也是 fmt.Sprintf 總是會使參數(shù)逃逸的原因。

例子非常多,這里不能一一列舉,我們只需要記住分析方法就好,即,2 級或更多級的訪問賦值會 容易 導(dǎo)致數(shù)據(jù)逃逸。這里加上 容易 二字是因?yàn)殡S著語言的發(fā)展,相信這些問題會被慢慢解決,但現(xiàn)階段,這個可以作為我們分析逃逸現(xiàn)象的依據(jù)。

下面代碼中包含 2 種很常規(guī)的寫法,但他們卻有著很大的性能差距,建議自己想下為什么。

Benchmark 和 pprof 給出的結(jié)果:

熟悉堆棧概念可以讓我們更容易看透 Go 程序的性能問題,并進(jìn)行優(yōu)化。

多級間接賦值會導(dǎo)致 Go 編譯器出現(xiàn)不必要的逃逸,在一些情況下可能我們只需要修改一下數(shù)據(jù)結(jié)構(gòu)就會使性能有大幅提升。這也是很多人不推薦在 Go 中使用指針的原因,因?yàn)樗鼤黾右患壴L問路徑,而 map , slice , interface{} 等類型是不可避免要用到的,為了減少不必要的逃逸,只能拿指針開刀了。

大多數(shù)情況下,性能優(yōu)化都會為程序帶來一定的復(fù)雜度。建議實(shí)際項(xiàng)目中還是怎么方便怎么寫,功能完成后通過性能分析找到瓶頸所在,再對局部進(jìn)行優(yōu)化。

golang內(nèi)存擴(kuò)容

一般來說當(dāng)內(nèi)存空間span不足時,需要進(jìn)行擴(kuò)容。而在擴(kuò)容前需要將當(dāng)前沒有剩余空間的內(nèi)存塊相關(guān)狀態(tài)解除,以便后續(xù)的垃圾回收期能夠進(jìn)行掃描和回收,接著在從中間部件(central)提取新的內(nèi)存塊放回?cái)?shù)組中。

需要注意由于中間部件有scan和noscan兩種類型,則申請的內(nèi)存空間最終獲取的可能是其兩倍,并由heap堆進(jìn)行統(tǒng)一管理。中間部件central是通過兩個鏈表來管理其分配的所有內(nèi)存塊:

1、empty代表“無法使用”狀態(tài),沒有剩余的空間或被移交給緩存的內(nèi)存塊

2、noempty代表剩余的空間,并這些內(nèi)存塊能夠提供服務(wù)

由于golang垃圾回收器使用的累增計(jì)數(shù)器(heap.sweepgen)來表達(dá)代齡的:

從上面內(nèi)容可以看到每次進(jìn)行清理操作時 該計(jì)數(shù)器 +2

再來看下mcentral的構(gòu)成

當(dāng)通過mcentral進(jìn)行空間span獲取時,第一步需要到noempty列表檢查剩余空間的內(nèi)存塊,這里面有一點(diǎn)需要說明主要是垃圾回收器的掃描過程和清理過程是同時進(jìn)行的,那么為了獲取更多的可用空間,則會在將分配的內(nèi)存塊移交給cache部件前,先完成清理的操作。第二步當(dāng)noempty沒有返回時,則需要檢查下empty列表(由于empty里的內(nèi)存塊有可能已被標(biāo)記為垃圾,這樣可以直接清理,對應(yīng)的空間則可直接使用了)。第三步若是noempty和empty都沒有申請到,這時需要堆進(jìn)行申請內(nèi)存的

通過上面的源碼也可以看到中間部件central自身擴(kuò)容操作與大對象內(nèi)存分配差不多類似。

在golang中將長度小于16bytes的對象稱為微小對象(tiny),最常見的就是小字符串,一般會將這些微小對象組合起來,并用單塊內(nèi)存存儲,這樣能夠有效的減少內(nèi)存浪費(fèi)。

當(dāng)微小對象需要分配空間span,首先緩存部件會按指定的規(guī)格(tiny size class)取出一塊內(nèi)存,若容量不足,則重新提取一塊;前面也提到會將微小對象進(jìn)行組合,而這些組合的微小對象是不能包含指針的,因?yàn)槔厥盏脑?,一般都是?dāng)前存儲單元里所有的微小對象都不可達(dá)時,才會將該塊內(nèi)存進(jìn)行回收。

而當(dāng)從緩沖部件cache中獲取空間span時, 是通過偏移位置(tinyoffset)先來判斷剩余空間是否滿足需求。若是可以的話則以此計(jì)算并返回內(nèi)存地址;若是空間不足,則提取新的內(nèi)存塊,直接返回起始地址便可; 最后在對比新舊兩塊內(nèi)存,空間大的那塊則會被保留。

Golang 1.14中內(nèi)存分配、清掃和內(nèi)存回收

Golang的內(nèi)存分配是由golang runtime完成,其內(nèi)存分配方案借鑒自tcmalloc。

主要特點(diǎn)就是

本文中的element指一定大小的內(nèi)存塊是內(nèi)存分配的概念,并為出現(xiàn)在golang runtime源碼中

本文講述x8664架構(gòu)下的內(nèi)存分配

Golang 內(nèi)存分配有下面幾個主要結(jié)構(gòu)

Tiny對象是指內(nèi)存尺寸小于16B的對象,這類對象的分配使用mcache的tiny區(qū)域進(jìn)行分配。當(dāng)tiny區(qū)域空間耗盡時刻,它會從mcache.alloc[tinySpanClass]指向的mspan中找到空閑的區(qū)域。當(dāng)然如果mcache中span空間也耗盡,它會觸發(fā)從mcentral補(bǔ)充mspan到mcache的流程。

小對象是指對象尺寸在(16B,32KB]之間的對象,這類對象的分配原則是:

1、首先根據(jù)對象尺寸將對象歸為某個SpanClass上,這個SpanClass上所有的element都是一個統(tǒng)一的尺寸。

2、從mcache.alloc[SpanClass]找到mspan,看看有無空閑的element,如果有分配成功。如果沒有繼續(xù)。

3、從mcentral.allocSpan[SpanClass]的nonempty和emtpy中找到合適的mspan,返回給mcache。如果沒有找到就進(jìn)入mcentral.grow()—mheap.alloc()分配新的mspan給mcentral。

大對象指尺寸超出32KB的對象,此時直接從mheap中分配,不會走mcache和mcentral,直接走mheap.alloc()分配一個SpanClass==0 的mspan表示這部分分配空間。

對于程序分配常用的tiny和小對象的分配,可以通過無鎖的mcache提升分配性能。mcache不足時刻會拿mcentral的鎖,然后從mcentral中充mspan 給mcache。大對象直接從mheap 中分配。

在x8664環(huán)境上,golang管理的有效的程序虛擬地址空間實(shí)質(zhì)上只有48位。在mheap中有一個pages pageAlloc成員用于管理golang堆內(nèi)存的地址空間。golang從os中申請地址空間給自己管理,地址空間申請下來以后,golang會將地址空間根據(jù)實(shí)際使用情況標(biāo)記為free或者alloc。如果地址空間被分配給mspan或大對象后,那么被標(biāo)記為alloc,反之就是free。

Golang認(rèn)為地址空間有以下4種狀態(tài):

Golang同時定義了下面幾個地址空間操作函數(shù):

在mheap結(jié)構(gòu)中,有一個名為pages成員,它用于golang 堆使用虛擬地址空間進(jìn)行管理。其類型為pageAlloc

pageAlloc 結(jié)構(gòu)表示的golang 堆的所有地址空間。其中最重要的成員有兩個:

在golang的gc流程中會將未使用的對象標(biāo)記為未使用,但是這些對象所使用的地址空間并未交還給os。地址空間的申請和釋放都是以golang的page為單位(實(shí)際以chunk為單位)進(jìn)行的。sweep的最終結(jié)果只是將某個地址空間標(biāo)記可被分配,并未真正釋放地址空間給os,真正釋放是后文的scavenge過程。

在gc mark結(jié)束以后會使用sweep()去嘗試free一個span;在mheap.alloc 申請mspan時刻,也使用sweep去清掃一下。

清掃mspan主要涉及到下面函數(shù)

如上節(jié)所述,sweep只是將page標(biāo)記為可分配,但是并未把地址空間釋放;真正的地址空間釋放是scavenge過程。

真正的scavenge是由pageAlloc.scavenge()—sysUnused()將掃描到待釋放的chunk所表示的地址空間釋放掉(使用sysUnused()將地址空間還給os)

golang的scavenge過程有兩種:

當(dāng)前標(biāo)題:go語言一個字符占用空間,go語言內(nèi)存占用
本文網(wǎng)址:http://www.muchs.cn/article24/hcpgje.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供外貿(mào)建站、搜索引擎優(yōu)化、關(guān)鍵詞優(yōu)化、營銷型網(wǎng)站建設(shè)、響應(yīng)式網(wǎng)站、品牌網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都app開發(fā)公司