Par rapport à c/c++, une grande amélioration de golang est l'introduction du mécanisme gc, qui n'oblige plus les utilisateurs à gérer eux-mêmes la mémoire, réduisant considérablement les bugs introduits par le programme en raison de fuites de mémoire, mais en même temps, gc entraîne également une surcharge de performances supplémentaire, et parfois même une utilisation inappropriée fait de gc un goulot d'étranglement en termes de performances. Par conséquent, lors de la conception de programmes Golang, une attention particulière doit être accordée à la réutilisation des objets pour réduire la pression sur. gc. Slice et string sont les types de base du golang. Comprendre les mécanismes internes de ces types de base nous aidera à mieux réutiliser ces objets
La structure interne de slice et de string
La structure interne de slice et de string peut être trouvée dans$GOROOT/src/reflect/value.go
type StringHeader struct { Data uintptr Len int } type SliceHeader struct { Data uintptr Len int Cap int }
Vous pouvez voir qu'une chaîne contient un pointeur de données et une longueur. La longueur est immuable.
Slice contient un pointeur de données, une longueur et une capacité. Lorsque la capacité n'est pas suffisante, une nouvelle mémoire sera réappliquée. le pointeur Data pointera vers la nouvelle adresse, l'espace d'adressage d'origine sera libéré
Il ressort de ces structures que l'affectation de la chaîne et de la tranche, y compris sa transmission en tant que paramètre, n'est qu'une copie superficielle du Pointeur de données comme la structure personnalisée
réutilisation des tranches
opération d'ajout
si1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} si2 := si1 si2 = append(si2, 0) Convey("重新分配内存", func() { header1 := (*reflect.SliceHeader)(unsafe.Pointer(&si1)) header2 := (*reflect.SliceHeader)(unsafe.Pointer(&si2)) fmt.Println(header1.Data) fmt.Println(header2.Data) So(header1.Data, ShouldNotEqual, header2.Data) })
si1 et si2 pointent tous deux vers le même tableau au début. Lorsque l'opération d'ajout est effectuée sur si2, car la valeur Cap d'origine n'est pas suffisante, un nouvel espace est nécessaire. à réappliquer, donc la valeur des données change, dans ce $GOROOT/src/reflect/value.go
Le fichier contient également des stratégies pour les nouvelles valeurs de plafond. Dans cette fonction, lorsque le plafond est inférieur à 1024, il augmentera de façon exponentielle. Lorsqu'il dépasse, il augmentera de 25 %. à chaque fois. Cette croissance de la mémoire n'est pas seulement une copie de données. (La copie de l'ancienne adresse vers la nouvelle adresse) nécessite des performances supplémentaires, et la libération de l'ancienne mémoire d'adresse entraînera également une charge supplémentaire sur gc, donc si vous pouvez connaître la longueur. des données, essayez d'utiliser grow
mémoire pré-allouée, non Lorsque vous connaissez la longueur, vous pouvez envisager la méthode de réutilisation de la mémoire suivantemake([]int, len, cap)
si1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
si2 := si1[:7]
Convey("不重新分配内存", func() {
header1 := (*reflect.SliceHeader)(unsafe.Pointer(&si1))
header2 := (*reflect.SliceHeader)(unsafe.Pointer(&si2))
fmt.Println(header1.Data)
fmt.Println(header2.Data)
So(header1.Data, ShouldEqual, header2.Data)
})
Convey("往切片里面 append 一个值", func() {
si2 = append(si2, 10)
Convey("改变了原 slice 的值", func() {
header1 := (*reflect.SliceHeader)(unsafe.Pointer(&si1))
header2 := (*reflect.SliceHeader)(unsafe.Pointer(&si2))
fmt.Println(header1.Data)
fmt.Println(header2.Data)
So(header1.Data, ShouldEqual, header2.Data)
So(si1[7], ShouldEqual, 10)
})
})
Copier après la connexion
si2 est une tranche de si1 Dès le premier morceau de code, vous. Nous pouvons voir que la tranche ne réalloue pas de mémoire. Les pointeurs Data de si2 et si1 pointent vers la même adresse, et comme le montre le deuxième morceau de code, lorsque nous ajoutons une nouvelle valeur à si2, nous constatons qu'il y en a toujours. pas d'allocation de mémoire, et cette opération entraîne également un changement de la valeur de si1, car les deux pointent à l'origine vers la même zone de données. En utilisant cette fonctionnalité, il suffit de laisser si1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} si2 := si1[:7] Convey("不重新分配内存", func() { header1 := (*reflect.SliceHeader)(unsafe.Pointer(&si1)) header2 := (*reflect.SliceHeader)(unsafe.Pointer(&si2)) fmt.Println(header1.Data) fmt.Println(header2.Data) So(header1.Data, ShouldEqual, header2.Data) }) Convey("往切片里面 append 一个值", func() { si2 = append(si2, 10) Convey("改变了原 slice 的值", func() { header1 := (*reflect.SliceHeader)(unsafe.Pointer(&si1)) header2 := (*reflect.SliceHeader)(unsafe.Pointer(&si2)) fmt.Println(header1.Data) fmt.Println(header2.Data) So(header1.Data, ShouldEqual, header2.Data) So(si1[7], ShouldEqual, 10) }) })
effacer continuellement le contenu de si1 pour réaliser la réutilisation de la mémoiresi1 = si1[:0]
PS : Vous pouvez utiliser pour implémenter une copie approfondiecopy(si2, si1)
Convey("字符串常量", func() {
str1 := "hello world"
str2 := "hello world"
Convey("地址相同", func() {
header1 := (*reflect.StringHeader)(unsafe.Pointer(&str1))
header2 := (*reflect.StringHeader)(unsafe.Pointer(&str2))
fmt.Println(header1.Data)
fmt.Println(header2.Data)
So(header1.Data, ShouldEqual, header2.Data)
})
})
Copier après la connexion
This L'exemple est relativement simple. Les constantes de chaîne utilisent la même zone d'adresse
Convey("字符串常量", func() { str1 := "hello world" str2 := "hello world" Convey("地址相同", func() { header1 := (*reflect.StringHeader)(unsafe.Pointer(&str1)) header2 := (*reflect.StringHeader)(unsafe.Pointer(&str2)) fmt.Println(header1.Data) fmt.Println(header2.Data) So(header1.Data, ShouldEqual, header2.Data) }) })
Convey("相同字符串的不同子串", func() { str1 := "hello world"[:6] str2 := "hello world"[:5] Convey("地址相同", func() { header1 := (*reflect.StringHeader)(unsafe.Pointer(&str1)) header2 := (*reflect.StringHeader)(unsafe.Pointer(&str2)) fmt.Println(header1.Data, str1) fmt.Println(header2.Data, str2) So(str1, ShouldNotEqual, str2) So(header1.Data, ShouldEqual, header2.Data) }) })
, et Nonstr1.Data == str2.Data && str1.Len == str2.Len
, l'exemple suivant peut illustrerstr1 == str2
mais ses données ne sont pas les mêmesstr1 == str2
Convey("不同字符串的相同子串", func() { str1 := "hello world"[:5] str2 := "hello golang"[:5] Convey("地址不同", func() { header1 := (*reflect.StringHeader)(unsafe.Pointer(&str1)) header2 := (*reflect.StringHeader)(unsafe.Pointer(&str2)) fmt.Println(header1.Data, str1) fmt.Println(header2.Data, str2) So(str1, ShouldEqual, str2) So(header1.Data, ShouldNotEqual, header2.Data) }) })
, sinon, cette chaîne elle-même est une sous-chaîne d'une autre chaîne (telle que la chaîne obtenue via []byte
). Cela ne s'appliquera pas à l'espace supplémentaire. Cela est tout simplement inutile. strings.Split