El principio de realización del corte en Golang.

Prefacio

En pocas palabras, un segmento es una versión simplificada de una matriz dinámica. Debido a que la longitud de la matriz dinámica no es fija, la longitud del segmento, naturalmente, no puede formar parte del tipo. Aunque las matrices son adecuadas para ellos, los tipos y operaciones de las matrices no son lo suficientemente flexibles, por lo que las matrices no se usan mucho en el código Go. El corte en rodajas es muy utilizado.

Análisis de código fuente de Slice

Nota: Comentaré la función de cada método en el código fuente, puede consultar los comentarios para comprenderlo.

Antes de mirar el código fuente, veamos un ejemplo simple

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

Creado por hacer una longitud de 0, la capacidad del segmento 0, segmento y luego agregar elementos adicionales por función
A continuación, analizamos el segmento y creamos un proceso adicional
desdeadjuntar() Es una función incorporada, por lo que solo podemos saberlo mirando su lenguaje ensambladoradjuntar() que pasó

Funciones integradas: Go language tiene algunas funciones integradas que se pueden usar sin operaciones de importación

Utilice el siguiente comando para ver el código pseudo ensamblador correspondiente al programa de lenguaje Go:

go tool compile -S slice.go

El comando de compilación de la herramienta go se utiliza para llamar a la herramienta de comando subyacente proporcionada por el lenguaje Go, y el parámetro -S indica el formato de ensamblado de salida.

		...
        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 ellos, solo debemos prestar atención a la instrucción CALL para ver qué llama.

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

La sexta línea de llamadas de slice.gohace rebanadaFunción, entonceshace rebanadaLa función se usa para inicializar el segmento

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

Similar,creceLa función es la
creación de secciones para agregar elementos.
A continuación, veamos cómo las secciones implementan estas funciones en el SDK de go.

Inicializar makelice

tiempo de ejecución \ slice.go

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

La definición de la estructura subyacente del sector es muy intuitiva, un puntero a la matriz subyacente, la longitud actual len y el límite del sector actual.
Echa un vistazo de nuevohace rebanadafunción

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)
}

Es tan simple que no hay mucho que decir. La función mallocgc encontrará la memoria apropiada en la lista enlazada del bloque de memoria correspondiente según el tamaño de memoria solicitado para la asignación, que es el conjunto de tcmalloc modificado por Go.

La expansión de datos crece

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}
}

Reglas de expansión:

  1. La nueva capacidad es 2 veces mayor que la anterior, se expande directamente a la nueva capacidad
  2. Cuando la nueva capacidad no es más de 2 veces la antigua
    1. Cuando la longitud anterior sea inferior a 1024, amplíe la capacidad al doble de la longitud anterior
    2. Ampliado a 1,25 veces la capacidad anterior

La expansión de los segmentos conducirá inevitablemente a la copia de la memoria. Si se trata de un sistema sensible al rendimiento, es una mejor opción asignar los segmentos lo antes posible.

Rebanada de copia de datos

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
}

para resumir

  • Intente establecer el valor de capacidad inicial para el segmento para evitar la expansión. La expansión del segmento conducirá inevitablemente a la copia de memoria
  • Un segmento es una estructura que contiene la capacidad, la longitud real y la dirección de la matriz.
  • Varias secciones pueden apuntar a la misma matriz subyacente
  • Las rebanadas se pasan por valor y solo pasan por valor en Go (pasan por valor)

Supongo que te gusta

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