ホームページ > バックエンド開発 > Golang > Golang のスライスと文字列の再利用について 1 つの記事で学ぶ

Golang のスライスと文字列の再利用について 1 つの記事で学ぶ

藏色散人
リリース: 2021-07-16 15:34:28
転載
2452 人が閲覧しました

c/c と比較した場合、golang の大きな改善点は gc メカニズムの導入であり、ユーザーがメモリを管理する必要がなくなりました。それ自体により、メモリ リークによってプログラムによってもたらされるバグが大幅に軽減されますが、同時に gc は追加のパフォーマンス オーバーヘッドももたらし、場合によっては不適切な使用により gc がパフォーマンスのボトルネックになることさえあります。 gc への負担を軽減するためにオブジェクトの再利用に注意を払う必要があります。スライスと文字列は golang の基本的なタイプです。これらの基本的なタイプの内部メカニズムを理解すると、これらのオブジェクトをより適切に再利用できるようになります。

スライスと文字列の内部構造

スライスと文字列の内部構造string 構造は $GOROOT/src/reflect/value.go

type StringHeader struct {
    Data uintptr
    Len  int
}

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}
ログイン後にコピー

にあります。文字列にはデータ ポインターと長さが含まれており、長さは不変であることがわかります

スライスにはデータ ポインタ、長さ、容量が含まれています。容量が十分でない場合は、新しいメモリが再適用されます。データ ポインタは新しいアドレスを指し、元のアドレス空間は解放されます。

これらの構造から、文字列とスライスの割り当て (パラメーターとして渡すことを含む) は、カスタム構造のようなデータ ポインターの単なる浅いコピーであることがわかります。

スライスの再利用

append 操作

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 と si2 は両方とも最初は同じ配列を指します。si2 で追加操作が実行されると、元の Cap 値が十分ではないため、新しいスペースを再適用する必要があります$GOROOT /src/reflect/value.go 内 このファイルには、新しい上限値の戦略も含まれています 関数 grow では、上限が小さくなったとき1024 よりも増加すると、25% 増加するたびに を超えて指数関数的に増加します。このメモリの増加は、データのコピー (古いアドレスから新しいアドレスへのコピー) に追加のパフォーマンスを消費するだけでなく、古いアドレスのメモリが解放されます。また、gc にさらなる負荷がかかるため、データの長さがわかっている場合は、make([]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)
    })
})
ログイン後にコピー

si2 は si1 のスライスです。コードの最初の部分から、スライスがメモリを再割り当てしないことがわかります。 si2 と si1 のデータ ポインターは同じスライス アドレスを指しますが、コードの 2 番目の部分は si2 に新しい値を追加すると、まだメモリ割り当てがないことがわかり、この操作により値が割り当てられます。両方とも同じデータ領域を指しているため、si1 の値も変更されます。この機能を使用すると、si1 = si1[:0] にするだけで、si1 の内容を継続的にクリアしてメモリの再利用を実現できます

PS: copy(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)
    })
})
ログイン後にコピー

この例は比較的単純です。文字列定数は同じアドレス領域を使用します

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)
    })
})
ログイン後にコピー

同じ文字列の異なる部分文字列は追加の新しいメモリには適用されませんが、ここで同じ文字列が str1.Data == str2 を参照することに注意してください。 .Data && str1.Len == str2. Lenstr1 == str2 の代わりに、次の例は str1 == 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)
    })
})
ログイン後にコピー

実際には文字列の場合、一つだけ覚えておいてください。文字列は不変です。文字列操作は追加のメモリには適用されません (内部データ ポインタのみ)。私はかつて、文字列を格納するキャッシュを巧みに設計しました。実際、文字列自体が []byte から作成されない限り、そうでない場合、文字列自体は別の文字列の部分文字列になります ( を通じて取得された文字列など) strings.Split) は追加のスペースには適用されません。

golang 関連の技術記事をさらに詳しく知りたい場合は、golang チュートリアル列をご覧ください。

以上がGolang のスライスと文字列の再利用について 1 つの記事で学ぶの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:segmentfault.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート