19

Go切片(Slice)浅析

 4 years ago
source link: https://studygolang.com/articles/25734
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

由于Go的Array创建之后长度就固定了,因此我们会更常用到Slice。 在本篇里我们将从Slice的创建,访问,增加,复制几个方面来简单了解下Slice的原理。

struct

下面是Slice在runtime的结构体

type slice struct {
	array unsafe.Pointer   // 指向切片头
	len   int   //  切片吧长度
	cap   int  // 切片容积
}

复制代码

创建

在编译时的Phase 6阶段,会进行逃逸分析,如果Slice发生逃逸或非常大时,会调用makeSlice64在堆上创建,否则就会在栈上或者静态存储区创建Slice。

walk

case OMAKESLICE:
  ...
  if n.Esc == EscNone {
    对于在栈上的将会转成以下类似代码
    // var arr [r]T
    // n = arr[:l]
    ...
  }else {
   对于在堆上创建的,将会调用makeslice64
   ...
    fnname := "makeslice64"
    ...
    m.Left = mkcall1(fn, types.Types[TUNSAFEPTR], init, typename(t.Elem()), conv(len, argtype), conv(cap, argtype))
    ...
  }

复制代码

makeslice

func makeslice(et *_type, len, cap int) unsafe.Pointer {
    校验cap长度
	mem, overflow := math.MulUintptr(et.size, uintptr(cap))
	if overflow || mem > maxAlloc || len < 0 || len > cap {
		// NOTE: Produce a 'len out of range' error instead of a
		// 'cap out of range' error when someone does make([]T, bignumber).
		// 'cap out of range' is true too, but since the cap is only being
		// supplied implicitly, saying len is clearer.
		// See golang.org/issue/4085.
		mem, overflow := math.MulUintptr(et.size, uintptr(len))
		if overflow || mem > maxAlloc || len < 0 {
			panicmakeslicelen()
		}
		panicmakeslicecap()
	}
    分配内存
	return mallocgc(mem, et, true)
}

复制代码

访问

Slice的访问在编译时就已经转化成了汇编代码,由于Slice的结构中的array指向的数组头的地址,而且Slice是一片连续的地址,访问就是从头往后数,具体逻辑在编译时的typecheck1的OINDEX中。

func typecheck1(n *Node, top int) (res *Node) {
    ...
    switch n.Op {
	case OINDEX:
	...
	switch t.Etype {
	    case TSTRING, TARRAY, TSLICE:
			n.Right = indexlit(n.Right)
			if t.IsString() {
				n.Type = types.Bytetype
			} else {
				n.Type = t.Elem()
			}
			why := "string"
			if t.IsArray() {
				why = "array"
			} else if t.IsSlice() {
				why = "slice"
			}
			...
	}
}

复制代码

增加

Slice的插入,删除等操作其实都是通过append函数来进行的,在append中,会经过一系列转化之后,调用到runtime的growslice方法,growslice会通过容量的大小进行不同的扩容策略,最后在进行迁移。

func growslice(et *_type, old slice, cap int) slice {
    ...
    通过容量进行扩容选择,减少频繁扩容之后进行迁移
	newcap := old.cap
	doublecap := newcap + newcap
	if cap > doublecap {
		newcap = cap
	} else {
		if old.len < 1024 {
			newcap = doublecap
		} else {
			// Check 0 < newcap to detect overflow
			// and prevent an infinite loop.
			for 0 < newcap && newcap < cap {
				newcap += newcap / 4
			}
			// Set newcap to the requested cap when
			// the newcap calculation overflowed.
			if newcap <= 0 {
				newcap = cap
			}
		}
	}
	...
	memmove(p, old.array, lenmem)

	return slice{p, old.len, newcap}
}

复制代码

复制

切片的复制是Slice在运行时里有实现,在编译时里,会通过HasHeapPointer来判断Slice是否存在堆上,是就会进行slicecopy操作,不是的话就会调用memmove对内存进行复制。

walk

func copyany(n *Node, init *Nodes, runtimecall bool) *Node {
    判断是否实在堆上创建
	if n.Left.Type.Elem().HasHeapPointer() {
		Curfn.Func.setWBPos(n.Pos)
		fn := writebarrierfn("typedslicecopy", n.Left.Type, n.Right.Type)
		return mkcall1(fn, n.Type, init, typename(n.Left.Type.Elem()), n.Left, n.Right)
	}
	....
	fn := syslook("memmove")
	fn = substArgTypes(fn, nl.Type.Elem(), nl.Type.Elem())
}
复制代码

slicecopy

func slicecopy(to, fm slice, width uintptr) int {
	if fm.len == 0 || to.len == 0 {
		return 0
	}

	n := fm.len
	if to.len < n {
		n = to.len
	}

	if width == 0 {
		return n
	}

	if raceenabled {
		callerpc := getcallerpc()
		pc := funcPC(slicecopy)
		racewriterangepc(to.array, uintptr(n*int(width)), callerpc, pc)
		racereadrangepc(fm.array, uintptr(n*int(width)), callerpc, pc)
	}
	if msanenabled {
		msanwrite(to.array, uintptr(n*int(width)))
		msanread(fm.array, uintptr(n*int(width)))
	}
    通过一系列判断最后走下面的if进行复制
	size := uintptr(n) * width
	if size == 1 { // common case worth about 2x to do here
		// TODO: is this still worth it with new memmove impl?
		*(*byte)(to.array) = *(*byte)(fm.array) // known to be a byte pointer
	} else {
		memmove(to.array, fm.array, size)
	}
	return n
}
复制代码

结语

  • 减少Slice的逃逸,给GC减负
  • 合理分配cap,减少append时的迁移

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK