go語言中的bit go語言中的函數(shù)

golang 實現(xiàn)bitmap

type bitmap struct {

東至網(wǎng)站建設公司創(chuàng)新互聯(lián)公司,東至網(wǎng)站設計制作,有大型網(wǎng)站制作公司豐富經(jīng)驗。已為東至成百上千提供企業(yè)網(wǎng)站建設服務。企業(yè)網(wǎng)站搭建\成都外貿(mào)網(wǎng)站制作要多少錢,請找那個售后服務好的東至做網(wǎng)站的公司定做!

keys []byte

len int

}

func NewBitMap() *bitmap {

return bitmap{keys:make([]byte, 0), len:0}

}

func (b *bitmap)has(v int) bool {

k := v /8

kv :=byte(v %8)

if k len(b.keys) { //todo not exist

? ????return false

}

if b.keys[k](1kv) != 0 {

return true

}

return false

}

func (b *bitmap)set(v int) {

k := v /8

kv :=byte(v %8)

for b.len = k {

b.keys =append(b.keys, 0)

b.len++

}

b.keys[k] =b.keys[k] | (1 kv)

}

func (b *bitmap)length()int {

return b.len

}

func (b *bitmap)print() {

for _, v :=range b.keys {

fmt.Printf("%08b\n", v)

}

}

(十一)golang 內(nèi)存分析

編寫過C語言程序的肯定知道通過malloc()方法動態(tài)申請內(nèi)存,其中內(nèi)存分配器使用的是glibc提供的ptmalloc2。 除了glibc,業(yè)界比較出名的內(nèi)存分配器有Google的tcmalloc和Facebook的jemalloc。二者在避免內(nèi)存碎片和性能上均比glic有比較大的優(yōu)勢,在多線程環(huán)境中效果更明顯。

Golang中也實現(xiàn)了內(nèi)存分配器,原理與tcmalloc類似,簡單的說就是維護一塊大的全局內(nèi)存,每個線程(Golang中為P)維護一塊小的私有內(nèi)存,私有內(nèi)存不足再從全局申請。另外,內(nèi)存分配與GC(垃圾回收)關系密切,所以了解GC前有必要了解內(nèi)存分配的原理。

為了方便自主管理內(nèi)存,做法便是先向系統(tǒng)申請一塊內(nèi)存,然后將內(nèi)存切割成小塊,通過一定的內(nèi)存分配算法管理內(nèi)存。 以64位系統(tǒng)為例,Golang程序啟動時會向系統(tǒng)申請的內(nèi)存如下圖所示:

預申請的內(nèi)存劃分為spans、bitmap、arena三部分。其中arena即為所謂的堆區(qū),應用中需要的內(nèi)存從這里分配。其中spans和bitmap是為了管理arena區(qū)而存在的。

arena的大小為512G,為了方便管理把arena區(qū)域劃分成一個個的page,每個page為8KB,一共有512GB/8KB個頁;

spans區(qū)域存放span的指針,每個指針對應一個page,所以span區(qū)域的大小為(512GB/8KB)乘以指針大小8byte = 512M

bitmap區(qū)域大小也是通過arena計算出來,不過主要用于GC。

span是用于管理arena頁的關鍵數(shù)據(jù)結構,每個span中包含1個或多個連續(xù)頁,為了滿足小對象分配,span中的一頁會劃分更小的粒度,而對于大對象比如超過頁大小,則通過多頁實現(xiàn)。

根據(jù)對象大小,劃分了一系列class,每個class都代表一個固定大小的對象,以及每個span的大小。如下表所示:

上表中每列含義如下:

class: class ID,每個span結構中都有一個class ID, 表示該span可處理的對象類型

bytes/obj:該class代表對象的字節(jié)數(shù)

bytes/span:每個span占用堆的字節(jié)數(shù),也即頁數(shù)乘以頁大小

objects: 每個span可分配的對象個數(shù),也即(bytes/spans)/(bytes/obj)waste

bytes: 每個span產(chǎn)生的內(nèi)存碎片,也即(bytes/spans)%(bytes/obj)上表可見最大的對象是32K大小,超過32K大小的由特殊的class表示,該class ID為0,每個class只包含一個對象。

span是內(nèi)存管理的基本單位,每個span用于管理特定的class對象, 跟據(jù)對象大小,span將一個或多個頁拆分成多個塊進行管理。src/runtime/mheap.go:mspan定義了其數(shù)據(jù)結構:

以class 10為例,span和管理的內(nèi)存如下圖所示:

spanclass為10,參照class表可得出npages=1,nelems=56,elemsize為144。其中startAddr是在span初始化時就指定了某個頁的地址。allocBits指向一個位圖,每位代表一個塊是否被分配,本例中有兩個塊已經(jīng)被分配,其allocCount也為2。next和prev用于將多個span鏈接起來,這有利于管理多個span,接下來會進行說明。

有了管理內(nèi)存的基本單位span,還要有個數(shù)據(jù)結構來管理span,這個數(shù)據(jù)結構叫mcentral,各線程需要內(nèi)存時從mcentral管理的span中申請內(nèi)存,為了避免多線程申請內(nèi)存時不斷的加鎖,Golang為每個線程分配了span的緩存,這個緩存即是cache。src/runtime/mcache.go:mcache定義了cache的數(shù)據(jù)結構

alloc為mspan的指針數(shù)組,數(shù)組大小為class總數(shù)的2倍。數(shù)組中每個元素代表了一種class類型的span列表,每種class類型都有兩組span列表,第一組列表中所表示的對象中包含了指針,第二組列表中所表示的對象不含有指針,這么做是為了提高GC掃描性能,對于不包含指針的span列表,沒必要去掃描。根據(jù)對象是否包含指針,將對象分為noscan和scan兩類,其中noscan代表沒有指針,而scan則代表有指針,需要GC進行掃描。mcache和span的對應關系如下圖所示:

mchache在初始化時是沒有任何span的,在使用過程中會動態(tài)的從central中獲取并緩存下來,跟據(jù)使用情況,每種class的span個數(shù)也不相同。上圖所示,class 0的span數(shù)比class1的要多,說明本線程中分配的小對象要多一些。

cache作為線程的私有資源為單個線程服務,而central則是全局資源,為多個線程服務,當某個線程內(nèi)存不足時會向central申請,當某個線程釋放內(nèi)存時又會回收進central。src/runtime/mcentral.go:mcentral定義了central數(shù)據(jù)結構:

lock: 線程間互斥鎖,防止多線程讀寫沖突

spanclass : 每個mcentral管理著一組有相同class的span列表

nonempty: 指還有內(nèi)存可用的span列表

empty: 指沒有內(nèi)存可用的span列表

nmalloc: 指累計分配的對象個數(shù)線程從central獲取span步驟如下:

將span歸還步驟如下:

從mcentral數(shù)據(jù)結構可見,每個mcentral對象只管理特定的class規(guī)格的span。事實上每種class都會對應一個mcentral,這個mcentral的集合存放于mheap數(shù)據(jù)結構中。src/runtime/mheap.go:mheap定義了heap的數(shù)據(jù)結構:

lock: 互斥鎖

spans: 指向spans區(qū)域,用于映射span和page的關系

bitmap:bitmap的起始地址

arena_start: arena區(qū)域首地址

arena_used: 當前arena已使用區(qū)域的最大地址

central: 每種class對應的兩個mcentral

從數(shù)據(jù)結構可見,mheap管理著全部的內(nèi)存,事實上Golang就是通過一個mheap類型的全局變量進行內(nèi)存管理的。mheap內(nèi)存管理示意圖如下:

系統(tǒng)預分配的內(nèi)存分為spans、bitmap、arean三個區(qū)域,通過mheap管理起來。接下來看內(nèi)存分配過程。

針對待分配對象的大小不同有不同的分配邏輯:

(0, 16B) 且不包含指針的對象: Tiny分配

(0, 16B) 包含指針的對象:正常分配

[16B, 32KB] : 正常分配

(32KB, -) : 大對象分配其中Tiny分配和大對象分配都屬于內(nèi)存管理的優(yōu)化范疇,這里暫時僅關注一般的分配方法。

以申請size為n的內(nèi)存為例,分配步驟如下:

Golang內(nèi)存分配是個相當復雜的過程,其中還摻雜了GC的處理,這里僅僅對其關鍵數(shù)據(jù)結構進行了說明,了解其原理而又不至于深陷實現(xiàn)細節(jié)。1、Golang程序啟動時申請一大塊內(nèi)存并劃分成spans、bitmap、arena區(qū)域

2、arena區(qū)域按頁劃分成一個個小塊。

3、span管理一個或多個頁。

4、mcentral管理多個span供線程申請使用

5、mcache作為線程私有資源,資源來源于mcentral。

go程序如何分配堆棧的

在Go語言中有一些調試技巧能幫助我們快速找到問題,有時候你想盡可能多的記錄異常但仍覺得不夠,搞清楚堆棧的意義有助于定位Bug或者記錄更完整的信息。

本文將討論堆棧跟蹤信息以及如何在堆棧中識別函數(shù)所傳遞的參數(shù)。

Functions

先從這段代碼開始:

Listing 1

01 package main

02

03 func main() {

04 ? ? slice := make([]string, 2, 4)

05 ? ? Example(slice, "hello", 10)

06 }

07

08 func Example(slice []string, str string, i int) {

09 ? ? panic("Want stack trace")

10 }

Example函數(shù)定義了3個參數(shù),1個string類型的slice, 1個string和1個integer, 并且拋出了panic,運行這段代碼可以看到這樣的結果:

Listing 2

Panic: Want stack trace

goroutine 1 [running]:

main.Example(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)

/Users/bill/Spaces/Go/Projects/src/github點抗 /goinaction/code/

temp/main.go:9 +0x64

main.main()

/Users/bill/Spaces/Go/Projects/src/github點抗 /goinaction/code/

temp/main.go:5 +0x85

goroutine 2 [runnable]:

runtime.forcegchelper()

/Users/bill/go/src/runtime/proc.go:90

runtime.goexit()

/Users/bill/go/src/runtime/asm_amd64.s:2232 +0x1

goroutine 3 [runnable]:

runtime.bgsweep()

/Users/bill/go/src/runtime/mgc0.go:82

runtime.goexit()

/Users/bill/go/src/runtime/asm_amd64.s:2232 +0x1

堆棧信息中顯示了在panic拋出這個時間所有的goroutines狀態(tài),發(fā)生的panic的goroutine會顯示在最上面。

Listing 3

01 goroutine 1 [running]:

02 main.Example(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)

/Users/bill/Spaces/Go/Projects/src/github點抗 /goinaction/code/

temp/main.go:9 +0x64

03 main.main()

/Users/bill/Spaces/Go/Projects/src/github點抗 /goinaction/code/

temp/main.go:5 +0x85

第1行顯示最先發(fā)出panic的是goroutine 1, 第二行顯示panic位于main.Example中, 并能定位到該行代碼,在本例中第9行引發(fā)了panic。

下面我們關注參數(shù)是如何傳遞的:

Listing 4

// Declaration

main.Example(slice []string, str string, i int)

// Call to Example by main.

slice := make([]string, 2, 4)

Example(slice, "hello", 10)

// Stack trace

main.Example(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)

這里展示了在main中帶參數(shù)調用Example函數(shù)時的堆棧信息,比較就能發(fā)現(xiàn)兩者的參數(shù)數(shù)量并不相同,Example定義了3個參數(shù),堆棧中顯示了6個參數(shù)。現(xiàn)在的關鍵問題是我們要弄清楚它們是如何匹配的。

第1個參數(shù)是string類型的slice,我們知道在Go語言中slice是引用類型,即slice變量結構會包含三個部分:指針、長度(Lengthe)、容量(Capacity)

Listing 5

// Slice parameter value

slice := make([]string, 2, 4)

// Slice header values

Pointer: ?0x2080c3f50

Length: ? 0x2

Capacity: 0x4

// Declaration

main.Example(slice []string, str string, i int)

// Stack trace

main.Example(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)

因此,前面3個參數(shù)會匹配slice, 如下圖所示:

Figure 1

figure provided by Georgi Knox

我們現(xiàn)在來看第二個參數(shù),它是string類型,string類型也是引用類型,它包括兩部分:指針、長度。

Listing 6

// String parameter value

"hello"

// String header values

Pointer: 0x425c0

Length: ?0x5

// Declaration

main.Example(slice []string,?str string, i int)

// Stack trace

main.Example(0x2080c3f50, 0x2, 0x4,?0x425c0, 0x5, 0xa)

可以確定,堆棧信息中第4、5兩個參數(shù)對應代碼中的string參數(shù),如下圖所示:

Figure 2

figure provided by Georgi Knox

最后一個參數(shù)integer是single word值。

Listing 7

// Integer parameter value

10

// Integer value

Base 16: 0xa

// Declaration

main.Example(slice []string, str string,?i int)

// Stack trace

main.Example(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5,?0xa)

現(xiàn)在我們可以匹配代碼中的參數(shù)到堆棧信息了。

Figure 3

figure provided by Georgi Knox

Methods

如果我們將Example作為結構體的方法會怎么樣呢?

Listing 8

01 package main

02

03 import "fmt"

04

05 type trace struct{}

06

07 func main() {

08 ? ? slice := make([]string, 2, 4)

09

10 ? ? var t trace

11 ? ? t.Example(slice, "hello", 10)

12 }

13

14 func (t *trace) Example(slice []string, str string, i int) {

15 ? ? fmt.Printf("Receiver Address: %p\n", t)

16 ? ? panic("Want stack trace")

17 }

如上所示修改代碼,將Example定義為trace的方法,并通過trace的實例t來調用Example。

再次運行程序,會發(fā)現(xiàn)堆棧信息有一點不同:

Listing 9

Receiver Address:?0x1553a8

panic: Want stack trace

01 goroutine 1 [running]:

02 main.(*trace).Example(0x1553a8, 0x2081b7f50, 0x2, 0x4, 0xdc1d0, 0x5, 0xa)

/Users/bill/Spaces/Go/Projects/src/github點抗 /goinaction/code/

temp/main.go:16 +0x116

03 main.main()

/Users/bill/Spaces/Go/Projects/src/github點抗 /goinaction/code/

temp/main.go:11 +0xae

首先注意第2行的方法調用使用了pointer receiver,在package名字和方法名之間多出了"*trace"字樣。另外,參數(shù)列表的第1個參數(shù)標明了結構體(t)地址。我們從堆棧信息中看到了內(nèi)部實現(xiàn)細節(jié)。

Packing

如果有多個參數(shù)可以填充到一個single word, 則這些參數(shù)值會合并打包:

Listing 10

01 package main

02

03 func main() {

04 ? ? Example(true, false, true, 25)

05 }

06?

07 func Example(b1, b2, b3 bool, i uint8) {

08 ? ? panic("Want stack trace")

09 }

這個例子修改Example函數(shù)為4個參數(shù):3個bool型和1個八位無符號整型。bool值也是用8個bit表示,所以在32位和64位架構下,4個參數(shù)可以合并為一個single word。

Listing 11

01 goroutine 1 [running]:

02 main.Example(0x19010001)

/Users/bill/Spaces/Go/Projects/src/github點抗 /goinaction/code/

temp/main.go:8 +0x64

03 main.main()

/Users/bill/Spaces/Go/Projects/src/github點抗 /goinaction/code/

temp/main.go:4 +0x32

這是本例的堆棧信息,看下圖的具體分析:

Listing 12

// Parameter values

true, false, true, 25

// Word value

Bits ? ?Binary ? ? ?Hex ? Value

00-07 ? 0000 0001 ??01? ??true

08-15 ? 0000 0000 ??00? ? false

16-23 ? 0000 0001 ??01? ? true

24-31 ? 0001 1001 ??19? ? 25

// Declaration

main.Example(b1, b2, b3 bool, i uint8)

// Stack trace

main.Example(0x19010001)

以上展示了參數(shù)值是如何匹配到4個參數(shù)的。當我們看到堆棧信息中包括十六進制值,需要知道這些值是如何傳遞的。

網(wǎng)站欄目:go語言中的bit go語言中的函數(shù)
URL鏈接:http://muchs.cn/article14/ddigode.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供外貿(mào)建站、響應式網(wǎng)站微信公眾號、、品牌網(wǎng)站制作、企業(yè)建站

廣告

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

綿陽服務器托管