作者 | 馬超
責編 | 夕顏
封圖 | CSDN下載自視覺中國
出品 | CSDN(ID:CSDNnews)
今年3月初,騰訊發佈了《騰訊研發大數據報告》,筆者發現GO語言的使用在鵝廠已經上升到了TOP5的位置了。
我們知道騰訊尤其是Docker容器化這一塊,是走在各大廠的前列的,尤其是他們的基於GO語言開發的DEVOPS藍鯨平臺,水平相當高。
經筆者實地上手體驗,GO語言在併發等方面還是相當優秀的,下面筆者就彙報一下最新的成果。
GO語言的切片簡介
切片(slice)是對數組的一個連續片段的引用,所以切片是一個引用類型同,與Python 中的 list 類型比較類似,這個片段可以是整個數組,也可以是由起始和終止索引標識的一些項的子集。Go語言中切片的內部結構包含地址、大小(len)和容量(cap)與數組相比切片最大的特點就是其容量是可變的。
GO語言的代碼解讀
1. append函數添加元素
Go語言的內建函數 append 可以為切片動態添加元素,不過需要注意的是,由於切片本身是變長的,因此在使用 append 函數為切片動態添加元素時,切片就會自動進行“擴容”,同時新切片的長度也會增加,但是有一點需要注意,append返回的是一個新的切片對象,而不是對原切片進行操作。在下面的代碼中我們先定義了一個切片a,並不斷通過append方式為其增加元素,並觀察切片a的長度及容量變化。
<code>package
main/<code>
<code>import (/<code><code>"fmt"
/<code><code>)/<code>
<code>func
main
{/<code>
<code>var
aint
/<code><code>/<code><code>/<code><code>/<code><code>/<code><code>/<code><code>/<code><code>/<code>
<code>/<code>
可以觀察到切片在擴容時,其容量(cap)的速度規律是以2 倍數進行的。
2.在切片中元素的刪除
刪除切片中開頭的N個元素
使用x = x[N:] 的方式來在切片中刪除由第i個元素開始的N個元素
具體代碼如下:
<code>package
main
/<code>
<code>import
(
/<code><code>"fmt"
/<code><code>)
/<code>
<code>func
main
{
/<code><code>var
a
=
int{1,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
}
//使用原始定義法來聲明並初始化一個切片
/<code><code>fmt.Println(a)
//運行結果為[1
2
3
4
5
6
7
8
9
10
]
/<code><code>a
=
a[1:]
//
刪除第1個元素
/<code><code>fmt.Println(a)
//刪頭第1個元素後,運行結果為[2
3
4
5
6
7
8
9
10
]
/<code><code>a
=
a[2:]
//
刪除前2個元素
/<code><code>fmt.Println(a)
//刪頭前2個元素後,運行結果為[4
5
6
7
8
9
10
]
/<code>
<code>}
/<code>
3、深入理解GO語言中的切片
有關切片的代碼位置在GOPATH\src\runtime\slice.go,其中對於幾個重點函數解讀如下:
1.slice 結構定義
首先slice是這樣一個結構體,他有一個存放數據的數組,和一個長度len與容量cap構成
<code>type
slicestruct
{/<code>
<code>array unsafe.Pointer /<code><code>len
int
/<code><code>cap
int
/<code><code>}/<code>
2.創建切片的makeslice函數
而創建切片的函數makeslice如下,可以看到函數會對於內存進行預分配,如果成功再正式分配內存,他建切片的makeslice函數源碼及註釋如下:
<code>func makeslice(et *_type,len
, cap int) slice {/<code>
<code> mem, overflow :=math
.MulUintptr(et.size, uintptr(cap))//此函數計算et.size也就是每個元素所佔空間的大小,並與容量cap相乘,其中mem既為所需要最大內存,overflow代表是否會造成溢出/<code><code>if
overflow || mem > maxAlloc ||len
0
||len
> cap {//判斷是否有溢出,長度為負數或者長度比容量大的情況,如存在 直接panic/<code><code>// NOTE: Produce a'len out of range'
error
instead of a/<code><code>//'cap out of range'
error
when someone does make([]T, bignumber)./<code><code>//'cap out of range'
istrue
too, but since the cap is only being/<code><code>// supplied implicitly, sayinglen
is clearer./<code><code>// See golang.org/issue/4085.
/<code><code>mem, overflow :=math
.MulUintptr(et.size, uintptr(len
))/<code><code>if
overflow || mem > maxAlloc ||len
0
{/<code><code>panicmakeslicelen/<code><code>}/<code><code>panicmakeslicecap/<code><code>}/<code><code>return
mallocgc(mem, et,true
)// 如果錯誤檢查成功,則分配內存,注意slice對象會被GC所自動清除。/<code>
<code>}/<code>
3.擴容函數growslice
通過閱讀growslice的源碼可以看在這個函數當中,擴容的規則是在長度小於1024時按照一直採用的是翻倍的方式進行擴容,在大於1024後,每次擴容至原容量的1.25倍,新容量計算完成後對於內存進行預分配,這點也makeslice的想法一致,接下再將老slice中的數據通過memmove(p, old.array, lenmem)的方式拷貝至新的slice。growlice函數源碼及註釋如下:
<code>func growslice(et *_type, old slice, cap int) slice {/<code>
<code>//
單純地擴容,不寫數據/<code><code>if
et.size ==0
{/<code><code>if
cap < old.cap {/<code><code> panic(errorString("growslice: cap out of range"
))/<code><code> }/<code><code> // append should not create a slice with nil pointer but non-zero len.
/<code><code>/
/ We assume that append doesn't need to preserve old.array in this case.
/<code><code>return slice{unsafe.Pointer(&zerobase), old.len, cap}
/<code><code>}
/<code><code>/
/ 擴容規則 1.新的容量大於舊的2倍,直接擴容至新的容量
/<code><code>/
/ 2.新的容量不大於舊的2倍,當舊的長度小於1024時,擴容至舊的2倍,否則擴容至舊的1.25倍
/<code><code>newcap := old.cap
/<code><code>doublecap := newcap + newcap
/<code><code>if cap > doublecap {
/<code><code>newcap = cap
/<code><code>} else {
/<code><code>if old.len
< 1024 {/<code><code>newcap = doublecap
/<code><code>} else {
/<code><code>for newcap < cap {
/<code><code>newcap += newcap /
4
/<code><code> }/<code><code> }/<code><code> }/<code>
<code>// 跟據切片類型和容量計算要分配內存的大小
/<code>
<code>var overflow bool
/<code><code>var lenmem, newlenmem, capmem uintptr
/<code><code>switch {
/<code><code>case et.size == 1:
/<code><code>lenmem = uintptr(old.len)
/<code><code>newlenmem = uintptr(cap)
/<code><code>capmem = roundupsize(uintptr(newcap))
/<code><code>overflow = uintptr(newcap) > maxAlloc
/<code><code>newcap = int(capmem)
/<code><code>case et.size == sys.PtrSize:
/<code><code>lenmem = uintptr(old.len) * sys.PtrSize
/<code><code>newlenmem = uintptr(cap) * sys.PtrSize
/<code><code>capmem = roundupsize(uintptr(newcap) * sys.PtrSize)
/<code><code>overflow = uintptr(newcap) > maxAlloc/sys
.PtrSize/<code><code>newcap = int(capmem / sys.PtrSize)/<code><code>case
isPowerOfTwo(et.size):
/<code><code>var
shift uintptr/<code><code>if sys.PtrSize ==8
{/<code><code>//
Mask shiftfor
better code generation./<code><code>shift = uintptr(sys.Ctz64(uint64(et.size))) &63
/<code><code>}else
{/<code><code>shift = uintptr(sys.Ctz32(uint32(et.size))) &31
/<code><code>}/<code><code>lenmem = uintptr(old.len) << shift/<code><code>newlenmem = uintptr(cap) << shift/<code><code>capmem = roundupsize(uintptr(newcap) << shift)/<code><code>overflow = uintptr(newcap) > (maxAlloc >> shift)/<code><code>newcap = int(capmem >> shift)/<code><code>default:
/<code><code>lenmem = uintptr(old.len) * et.size/<code><code>newlenmem = uintptr(cap) * et.size/<code><code>capmem, overflow = math.MulUintptr(et.size, uintptr(newcap))/<code><code>capmem = roundupsize(capmem)/<code><code>newcap = int(capmem / et.size)/<code><code>}/<code><code>// 異常情況,舊的容量比新的容量還大或者新的容量超過限制了
/<code><code>if cap < old.cap || uintptr(newcap) > maxSliceCap(et.size) {
/<code><code>panic(errorString("growslice: cap out of range"))
/<code><code>}
/<code>
<code>var p unsafe.Pointer
/<code><code>if et.kind&kindNoPointers != 0 {
/<code>
<code>/
/ 為新的切片開闢容量為capmem的地址空間
/<code><code>p = mallocgc(capmem, nil, false)
/<code><code>/
/ 將舊切片的數據搬到新切片開闢的地址中
/<code><code>memmove(p, old.array, lenmem)
/<code><code>/
/ The append that calls growslice is going to overwrite from old.len to cap (which will be the new length).
/<code><code>/
/ Only clear the part that will not be overwritten.
/<code><code>/
/ 清理下新切片中剩餘地址,不能存放堆棧指針
/<code>
<code>/
/ memclrNoHeapPointers clears n bytes starting at ptr.
/<code><code>/
/
/<code><code>/
/ Usually you should use typedmemclr. memclrNoHeapPointers should be
/<code><code>/
/ used only when the caller knows that *ptr contains no heap pointers
/<code><code>/
/ because either:
/<code><code>/
/
/<code><code>/
/ 1. *ptr is initialized memory and its type is pointer-free.
/<code><code>/
/
/<code><code>/
/ 2. *ptr is uninitialized memory (e.g., memory that's being reused
/<code><code>/
/ for a new allocation) and hence contains only "junk".
/<code><code>memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
/<code><code>} else {
/<code><code>/
/ Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory.
/<code><code>p = mallocgc(capmem, et, true)
/<code><code>if !writeBarrier.enabled {
/<code><code>memmove(p, old.array, lenmem)
/<code><code>} else {
/<code><code>for i := uintptr(0); i < lenmem; i += et.size {
/<code><code>typedmemmove(et, add(p, i), add(old.array, i))
/<code><code>}
/<code><code>}
/<code><code>}
/<code>
<code>return slice{p, old.len, newcap}
/<code><code>}
/<code>
GO語言切片的相關結論
所以通過閱讀以上源代碼我們也可以知道,有以下兩點結論:
append方式為數據增加元素時,如果觸發切片進行擴容,則肯定是新生成了一個切片對象,並且涉及內存操作,因此append操作一定要小心。
建議儘量通過make函數來聲明一個切片,並在初始時儘量設定好一個合理的容量值,避免切片頻繁擴容帶來不必要的開銷。
原文鏈接:
https://blog.csdn.net/BEYONDMA/article/details/104799500
今日福利
遇見陸奇
同樣作為“百萬人學 AI”的重要組成部分,2020 AIProCon 開發者萬人大會將於 7 月 3 日至 4 日通過線上直播形式,讓開發者們一站式學習瞭解當下 AI 的前沿技術研究、核心技術與應用以及企業案例的實踐經驗,同時還可以在線參加精彩多樣的開發者沙龍與編程項目。參與前瞻系列活動、在線直播互動,不僅可以與上萬名開發者們一起交流,還有機會贏取直播專屬好禮,與技術大咖連麥。