Bagaimana Anda membersihkan sepotong di Go?

125

Apa cara yang tepat untuk membersihkan irisan di Go?

Inilah yang saya temukan di forum go :

// test.go
package main

import (
    "fmt"
)

func main() {
    letters := []string{"a", "b", "c", "d"}
    fmt.Println(cap(letters))
    fmt.Println(len(letters))
    // clear the slice
    letters = letters[:0]
    fmt.Println(cap(letters))
    fmt.Println(len(letters))
}

Apakah ini benar?

Untuk memperjelas, buffer dihapus sehingga dapat digunakan kembali.

Contohnya adalah fungsi Buffer.Truncate dalam paket byte.

Perhatikan bahwa Reset hanya memanggil Truncate (0). Jadi terlihat bahwa dalam kasus ini baris 70 akan mengevaluasi: b.buf = b.buf [0: 0]

http://golang.org/src/pkg/bytes/buffer.go

// Truncate discards all but the first n unread bytes from the buffer.
60  // It panics if n is negative or greater than the length of the buffer.
61  func (b *Buffer) Truncate(n int) {
62      b.lastRead = opInvalid
63      switch {
64      case n < 0 || n > b.Len():
65          panic("bytes.Buffer: truncation out of range")
66      case n == 0:
67          // Reuse buffer space.
68          b.off = 0
69      }
70      b.buf = b.buf[0 : b.off+n]
71  }
72  
73  // Reset resets the buffer so it has no content.
74  // b.Reset() is the same as b.Truncate(0).
75  func (b *Buffer) Reset() { b.Truncate(0) }
Chris Weber
sumber
1
Sebuah tes cepat di: play.golang.org/p/6Z-qDQtpbg tampaknya menunjukkan bahwa itu akan berhasil (tidak akan mengubah kapasitas tetapi akan memotong panjangnya)
Jason Sperske

Jawaban:

120

Itu semua tergantung pada apa definisi Anda tentang 'jelas'. Salah satu yang valid tentunya adalah:

slice = slice[:0]

Tapi ada tangkapan. Jika elemen irisan berjenis T:

var slice []T

kemudian memaksakan len(slice)menjadi nol, dengan "trik" di atas, tidak membuat elemen apa pun

slice[:cap(slice)]

memenuhi syarat untuk pengumpulan sampah. Ini mungkin pendekatan yang optimal dalam beberapa skenario. Tetapi ini juga bisa menjadi penyebab "kebocoran memori" - memori tidak digunakan, tetapi berpotensi dapat dijangkau (setelah pemotongan ulang 'irisan') dan dengan demikian bukan sampah "dapat dikoleksi".

zzzz
sumber
1
Menarik. Apakah ada cara lain untuk menghapus semua elemen dari larik yang mendasari irisan sambil membiarkan kapasitas yang mendasarinya tidak berubah?
Chris Weber
3
@ChrisWeber: cukup lakukan iterasi pada array yang mendasarinya dan setel semua elemen ke nilai baru
newacct
2
@jnml, saya ingin menggunakan kembali slice (dan penyimpanan array yang mendasarinya) jadi saya tidak terus-menerus mengalokasikan slice baru (dengan array). Saya telah mengedit pertanyaan saya untuk memperjelas dan untuk menunjukkan beberapa contoh kode dari pustaka standar.
Chris Weber
1
Saya baru mengenal Go. Bisakah Anda menjelaskan lebih lanjut tentang mengapa ini bisa menjadi pendekatan yang optimal? Terima kasih sebelumnya.
satoru
Apakah Anda yakin bahwa penyetelan ulang ukuran irisan menyebabkan kebocoran memori? Saya tidak dapat mereproduksinya
Tommaso Barbugli
197

Mengatur irisan ke niladalah cara terbaik untuk membersihkan irisan. nilslice yang sedang berjalan berperilaku sangat baik dan menyetel slice ke nilakan melepaskan memori yang mendasarinya ke pengumpul sampah.

Lihat taman bermain

package main

import (
    "fmt"
)

func dump(letters []string) {
    fmt.Println("letters = ", letters)
    fmt.Println(cap(letters))
    fmt.Println(len(letters))
    for i := range letters {
        fmt.Println(i, letters[i])
    }
}

func main() {
    letters := []string{"a", "b", "c", "d"}
    dump(letters)
    // clear the slice
    letters = nil
    dump(letters)
    // add stuff back to it
    letters = append(letters, "e")
    dump(letters)
}

Cetakan

letters =  [a b c d]
4
4
0 a
1 b
2 c
3 d
letters =  []
0
0
letters =  [e]
1
1
0 e

Perhatikan bahwa irisan dapat dengan mudah diberi alias sehingga dua irisan mengarah ke memori dasar yang sama. Pengaturan untuk nilakan menghapus aliasing itu.

Metode ini mengubah kapasitas menjadi nol.

Nick Craig-Wood
sumber
Nick terima kasih atas tanggapannya. Silakan lihat pembaruan saya jika Anda mau. Saya membersihkan potongan untuk digunakan kembali. Jadi saya tidak ingin memori yang mendasari dilepaskan ke GC karena saya hanya perlu mengalokasikannya lagi.
Chris Weber
itulah yang saya cari!)
Timur Fayzrakhmanov
5
Berdasarkan judul "Bagaimana cara membersihkan sepotong di Go?" Sejauh ini, ini adalah jawaban yang lebih aman dan harus diterima. Namun, jawaban yang sempurna adalah kombinasi dari jawaban yang diterima semula dan yang ini sehingga orang dapat memutuskan sendiri.
Shadoninja
1
appending to a nilslice selalu berhasil di Go?
alediaferia
@alediaferia sejak go 1.0 tentunya.
Nick Craig-Wood
4

Saya melihat masalah ini sedikit untuk tujuan saya sendiri; Saya memiliki sepotong struct (termasuk beberapa petunjuk) dan saya ingin memastikan bahwa saya melakukannya dengan benar; berakhir di utas ini, dan ingin membagikan hasil saya.

Untuk berlatih, saya melakukan sedikit bermain: https://play.golang.org/p/9i4gPx3lnY

yang menunjukkan ini:

package main

import "fmt"

type Blah struct {
    babyKitten int
    kittenSays *string
}

func main() {
    meow := "meow"
    Blahs := []Blah{}
    fmt.Printf("Blahs: %v\n", Blahs)
    Blahs = append(Blahs, Blah{1, &meow})
    fmt.Printf("Blahs: %v\n", Blahs)
    Blahs = append(Blahs, Blah{2, &meow})
    fmt.Printf("Blahs: %v\n", Blahs)
    //fmt.Printf("kittenSays: %v\n", *Blahs[0].kittenSays)
    Blahs = nil
    meow2 := "nyan"
    fmt.Printf("Blahs: %v\n", Blahs)
    Blahs = append(Blahs, Blah{1, &meow2})
    fmt.Printf("Blahs: %v\n", Blahs)
    fmt.Printf("kittenSays: %v\n", *Blahs[0].kittenSays)
}

Menjalankan kode itu apa adanya akan menampilkan alamat memori yang sama untuk variabel "meow" dan "meow2" sebagai:

Blahs: []
Blahs: [{1 0x1030e0c0}]
Blahs: [{1 0x1030e0c0} {2 0x1030e0c0}]
Blahs: []
Blahs: [{1 0x1030e0f0}]
kittenSays: nyan

yang menurut saya menegaskan bahwa struct adalah sampah yang dikumpulkan. Anehnya, menghapus komentar pada baris cetak yang dikomentari, akan menghasilkan alamat memori yang berbeda untuk meow:

Blahs: []
Blahs: [{1 0x1030e0c0}]
Blahs: [{1 0x1030e0c0} {2 0x1030e0c0}]
kittenSays: meow
Blahs: []
Blahs: [{1 0x1030e0f8}]
kittenSays: nyan

Saya pikir ini mungkin karena pencetakan yang ditangguhkan dalam beberapa cara (?), Tetapi ilustrasi yang menarik dari beberapa perilaku manajemen memori, dan satu suara lagi untuk:

[]MyStruct = nil
max garvey
sumber
Contoh detail yang bagus. Terima kasih!
Dolanor
2
Ini tidak menunjukkan alamat memori meo1 dan meow2 sama: 0x1030e0c0tidak sama dengan 0x1030e0f0(yang pertama diakhiri dengan c0, yang terakhir masuk f0).
karbokation
Harus setuju dengan @carbocation di sini, alamat memori itu tidak sama. Saya tidak mengklaim dapat menjelaskan dengan lebih baik apa yang terjadi di sini, tetapi ini tidak menjadi bukti bagi saya. Saya memang melihat perbedaan 8-byte yang sama di alamat meow2setiap proses ...
rbrtl