Maison  >  Article  >  développement back-end  >  Découvrez la réutilisation de la tranche et de la chaîne de Golang dans un seul article

Découvrez la réutilisation de la tranche et de la chaîne de Golang dans un seul article

藏色散人
藏色散人avant
2021-07-16 15:34:282341parcourir

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)

Réutilisation de la mémoire

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

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

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)

string

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

This L'exemple est relativement simple. Les constantes de chaîne utilisent la même zone d'adresse

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)
    })
})
différentes sous-chaînes de la même chaîne. Aucune nouvelle mémoire supplémentaire ne sera demandée. . Cependant, il convient de noter que la même chaîne fait ici référence à

, 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)
    })
})
En fait, pour les chaînes, vous n'avez qu'à retenir une chose, les chaînes sont immuables et aucune opération sur les chaînes ne s'appliquera à la mémoire supplémentaire (pour les pointeurs de données internes uniquement). J'ai déjà intelligemment conçu un cache pour stocker les chaînes afin de réduire l'espace occupé par les chaînes répétées. En fait, à moins que la chaîne elle-même ne soit créée par.

, 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

Pour plus d'articles techniques liés au golang, veuillez visiter la colonne didacticiel

golang !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer