O princípio de realização da fatia em Golang

Prefácio

Simplificando, uma fatia é uma versão simplificada de uma matriz dinâmica. Como o comprimento da matriz dinâmica não é fixo, o comprimento da fatia naturalmente não pode fazer parte do tipo. Embora os arrays sejam adequados para eles, os tipos e operações dos arrays não são flexíveis o suficiente, então os arrays não são muito usados ​​no código Go. O fatiamento é amplamente utilizado.

Análise do código-fonte da fatia

Nota: Vou comentar a função de cada método no código-fonte, você pode consultar os comentários para compreensão.

Antes de olhar para o código-fonte, vamos dar uma olhada em um exemplo simples

func main() {
    
    
	slice:=make([]int,0) //第6行
	slice=append(slice,1)//第7行
}

Criado por fazer um comprimento de 0, a capacidade da fatia 0, fatia então para anexar elementos adicionais por função
Em seguida, analisamos a fatia e criamos processos adicionais
desdeacrescentar() É uma função embutida, então só podemos saber olhando para sua linguagem assemblyacrescentar() o que aconteceu

Funções integradas: a linguagem Go tem algumas funções integradas que podem ser usadas sem operações de importação

Use o seguinte comando para visualizar o código de pseudo assembly correspondente ao programa de linguagem Go:

go tool compile -S slice.go

O comando de compilação da ferramenta go é usado para chamar a ferramenta de comando subjacente fornecida pela linguagem Go, e o parâmetro -S indica o formato do assembly de saída.

		...
        0x0028 00040 (slice.go:6)       PCDATA  $2, $1
        0x0028 00040 (slice.go:6)       PCDATA  $0, $0
        0x0028 00040 (slice.go:6)       LEAQ    type.int(SB), AX
        0x002f 00047 (slice.go:6)       PCDATA  $2, $0
        0x002f 00047 (slice.go:6)       MOVQ    AX, (SP)
        0x0033 00051 (slice.go:6)       XORPS   X0, X0
        0x0036 00054 (slice.go:6)       MOVUPS  X0, 8(SP)
        0x003b 00059 (slice.go:6)       CALL    runtime.makeslice(SB)
        0x0040 00064 (slice.go:6)       PCDATA  $2, $1
        0x0040 00064 (slice.go:6)       MOVQ    24(SP), AX
        0x0045 00069 (slice.go:7)       PCDATA  $2, $2
        0x0045 00069 (slice.go:7)       LEAQ    type.int(SB), CX
        0x004c 00076 (slice.go:7)       PCDATA  $2, $1
        0x004c 00076 (slice.go:7)       MOVQ    CX, (SP)
        0x0050 00080 (slice.go:7)       PCDATA  $2, $0
        0x0050 00080 (slice.go:7)       MOVQ    AX, 8(SP)
        0x0055 00085 (slice.go:7)       XORPS   X0, X0
        0x0058 00088 (slice.go:7)       MOVUPS  X0, 16(SP)
        0x005d 00093 (slice.go:7)       MOVQ    $1, 32(SP)
        0x0066 00102 (slice.go:7)       CALL    runtime.growslice(SB)
        0x006b 00107 (slice.go:7)       PCDATA  $2, $1
        0x006b 00107 (slice.go:7)       MOVQ    40(SP), AX
        0x0070 00112 (slice.go:7)       MOVQ    48(SP), CX
        0x0075 00117 (slice.go:7)       MOVQ    56(SP), DX
        0x007a 00122 (slice.go:7)       MOVQ    $1, (AX)
		...

Entre eles, basta prestar atenção à instrução CALL para ver o que ela chama.

 0x003b 00059 (slice.go:6)       CALL    runtime.makeslice(SB)

A sexta linha de slice.go chamafaz piolhoFunção, entãofaz piolhoA função é usada para inicializar a fatia

0x0066 00102 (slice.go:7)       CALL    runtime.growslice(SB)

Similarmente,piolhoFunções são o
makelice para anexar elementos. A
seguir, vamos dar uma olhada em como as fatias implementam essas funções no Go SDK.

Inicializar o makelice

runtime \ slice.go

type slice struct {
    
    
	array unsafe.Pointer
	len   int
	cap   int
}

A definição da estrutura subjacente da fatia é muito intuitiva, um ponteiro para a matriz subjacente, o comprimento atual len e o limite da fatia atual.
Dê uma olhada novamentefaz piolhofunção

func makeslice(et *_type, len, cap int) unsafe.Pointer {
    
    
	// 获取需要申请的内存大小
	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()
	}
	// 分配内存 
  	// 小对象从当前P 的cache中空闲数据中分配
  	// 大的对象 (size > 32KB) 直接从heap中分配
	return mallocgc(mem, et, true)
}

É tão simples, não há nada a dizer. A função mallocgc encontrará a memória apropriada na lista vinculada de blocos de memória correspondente de acordo com o tamanho de memória solicitado para alocação, que é o conjunto de tcmalloc modificado por Go.

Expansão de dados growslice

func growslice(et *_type, old slice, cap int) slice {
    
    
	//扫描内存
	if raceenabled {
    
    
		callerpc := getcallerpc()
		racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice))
	}
	if msanenabled {
    
    
		msanread(old.array, uintptr(old.len*int(et.size)))
	}
	//容量判断
	if cap < old.cap {
    
    
		panic(errorString("growslice: cap out of range"))
	}
	//如果存储的类型空间为0,  比如说 []struct{}, 数据为空,长度不为空
	if et.size == 0 {
    
    
		// append should not create a slice with nil pointer but non-zero len.
		// We assume that append doesn't need to preserve old.array in this case.
		return slice{
    
    unsafe.Pointer(&zerobase), old.len, cap}
	}

	newcap := old.cap
	doublecap := newcap + newcap
	//如果新的容量大于旧的两倍,则直接扩容到新的容量
	if cap > doublecap {
    
    
		newcap = cap
	} else {
    
    
	//当新的容量不大于旧的两倍
		// 如果旧长度小于1024,那扩容到旧的两倍
		if old.len < 1024 {
    
    
			newcap = doublecap
		} else {
    
    
			//否则扩容到旧的1.25倍
			for 0 < newcap && newcap < cap {
    
    
				newcap += newcap / 4
			}
			//检查newCap是否溢出
			if newcap <= 0 {
    
    
				newcap = cap
			}
		}
	}

	var overflow bool
	var lenmem, newlenmem, capmem uintptr
	// 为了加速计算
    // 对于不同的slice元素大小,选择不同的计算方法
    // 获取需要申请的内存大小。
	switch {
    
    
	case et.size == 1:
		lenmem = uintptr(old.len)
		newlenmem = uintptr(cap)
		capmem = roundupsize(uintptr(newcap))
		overflow = uintptr(newcap) > maxAlloc
		newcap = int(capmem)
	case et.size == sys.PtrSize:
		lenmem = uintptr(old.len) * sys.PtrSize
		newlenmem = uintptr(cap) * sys.PtrSize
		capmem = roundupsize(uintptr(newcap) * sys.PtrSize)
		overflow = uintptr(newcap) > maxAlloc/sys.PtrSize
		newcap = int(capmem / sys.PtrSize)
	case isPowerOfTwo(et.size):
		//如果是二的倍数,用位移运算
		var shift uintptr
		if sys.PtrSize == 8 {
    
    
			// Mask shift for better code generation.
			shift = uintptr(sys.Ctz64(uint64(et.size))) & 63
		} else {
    
    
			shift = uintptr(sys.Ctz32(uint32(et.size))) & 31
		}
		lenmem = uintptr(old.len) << shift
		newlenmem = uintptr(cap) << shift
		capmem = roundupsize(uintptr(newcap) << shift)
		overflow = uintptr(newcap) > (maxAlloc >> shift)
		newcap = int(capmem >> shift)
	default:
		//默认用除法
		lenmem = uintptr(old.len) * et.size
		newlenmem = uintptr(cap) * et.size
		capmem, overflow = math.MulUintptr(et.size, uintptr(newcap))
		capmem = roundupsize(capmem)
		newcap = int(capmem / et.size)
	}

	// 判断是否会溢出
	if overflow || capmem > maxAlloc {
    
    
		panic(errorString("growslice: cap out of range"))
	}

	var p unsafe.Pointer
	if et.kind&kindNoPointers != 0 {
    
    
		//为新的切片开辟容量为capmem的地址空间
		p = mallocgc(capmem, nil, false)
		// 仅清除不会被覆盖的部分。
		memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
	} else {
    
    
		// 注意:不能使用rawmem(这样可以避免内存清零),因为GC可以扫描未初始化的内存。
		p = mallocgc(capmem, et, true)
		if writeBarrier.enabled {
    
    
			//因为我们知道目标切片p,所以仅将指针隐藏在old.array中
			//仅包含nil指针,因为在分配过程中已将其清除。
			bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem)
		}
	}
	//数据拷贝
	memmove(p, old.array, lenmem)

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

Regras de expansão:

  1. A nova capacidade é 2 vezes maior do que a antiga, expandir diretamente para a nova capacidade
  2. Quando a nova capacidade não é mais do que 2 vezes a antiga
    1. Quando o comprimento antigo for inferior a 1024, expanda para o dobro do comprimento antigo
    2. Expandido para 1,25 vezes a capacidade anterior

A expansão de fatias inevitavelmente levará à cópia da memória. Se for um sistema sensível ao desempenho, é uma escolha melhor alocar fatias o mais cedo possível.

Cópia de fatias de cópia de dados

func slicecopy(to, fm slice, width uintptr) int {
    
    
	//从slice长度为0或者到slice长度为0
	if fm.len == 0 || to.len == 0 {
    
    
		return 0
	}

	n := fm.len
	if to.len < n {
    
    
		n = to.len
	}
	//长度为0,直接返回
	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)))
	}
	
	size := uintptr(n) * width
	if size == 1 {
    
     
		// 直接内存拷贝
		*(*byte)(to.array) = *(*byte)(fm.array) // known to be a byte pointer
	} else {
    
    
		memmove(to.array, fm.array, size)
	}
	return n
}

Resumindo

  • Tente definir o valor da capacidade inicial da fatia para evitar a expansão. A expansão da fatia inevitavelmente levará à cópia da memória
  • Uma fatia é uma estrutura que contém a capacidade, o comprimento real e o endereço da matriz
  • Várias fatias podem apontar para a mesma matriz subjacente
  • Fatias são passadas por valor e só passam por valor em Go (passam por valor)

Acho que você gosta

Origin blog.csdn.net/xzw12138/article/details/107225548
Recomendado
Clasificación