Jak wyczyścić plasterek w Go?

125

Jaki jest właściwy sposób na usunięcie kawałka w Go?

Oto, co znalazłem na forach 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))
}

Czy to jest poprawne?

Aby wyjaśnić, bufor jest czyszczony, aby można go było ponownie wykorzystać.

Przykładem jest funkcja Buffer.Truncate w pakiecie bajtów.

Zauważ, że Reset wywołuje po prostu Truncate (0). Wygląda więc na to, że w tym przypadku wiersz 70 wyliczyłby: 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
źródło
1
Szybki test na: play.golang.org/p/6Z-qDQtpbg wydaje się sugerować, że to zadziała (nie zmieni pojemności, ale
skróci

Odpowiedzi:

120

Wszystko zależy od tego, jaka jest Twoja definicja „jasności”. Jednym z ważnych z pewnością jest:

slice = slice[:0]

Ale jest haczyk. Jeśli elementy plastra są typu T:

var slice []T

następnie wymuszenie wartości len(slice)zero przez powyższą "sztuczkę" nie tworzy żadnego elementu

slice[:cap(slice)]

kwalifikuje się do czyszczenia pamięci. To może być optymalne podejście w niektórych scenariuszach. Ale może to być również przyczyną „wycieków pamięci” - pamięć nieużywana, ale potencjalnie dostępna (po ponownym pocięciu „plasterka”), a zatem nie „śmieciowa” do „zbierania”.

zzzz
źródło
1
Ciekawy. Czy istnieje inny sposób usunięcia wszystkich elementów z podstawowej tablicy wycinka, pozostawiając niezmienioną pojemność?
Chris Weber,
3
@ChrisWeber: po prostu wykonaj iterację po podstawowej tablicy i ustaw wszystkie elementy na nową wartość
newacct
2
@jnml, chcę ponownie użyć tego wycinka (i podstawowej pamięci tablicowej), więc nie przydzielam ciągle nowego wycinka (z tablicą). Zredagowałem moje pytanie, aby wyjaśnić i pokazać przykładowy kod z biblioteki standardowej.
Chris Weber,
1
Jestem nowy w Go. Czy mógłbyś wyjaśnić więcej, dlaczego może to być optymalne podejście? Z góry dziękuję.
satoru
Czy na pewno resetowanie rozmiaru wycinka powoduje wycieki pamięci? Nie jestem w stanie tego odtworzyć
Tommaso Barbugli
197

Ustawienie plasterka nilto najlepszy sposób na wyczyszczenie plastra. nilplastry w ruchu zachowują się doskonale, a ustawienie tego plasterka nilspowoduje zwolnienie podstawowej pamięci do modułu odśmiecania pamięci.

Zobacz plac zabaw

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

Wydruki

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

Zauważ, że plasterki można łatwo aliasować, tak aby dwa wycinki wskazywały na tę samą pamięć podstawową. Ustawienie na nilusunie aliasing.

Ta metoda zmienia jednak pojemność na zero.

Nick Craig-Wood
źródło
Nick dzięki odpowiedzi. Proszę zobaczyć moją aktualizację. Czyszczę plasterek do ponownego użycia. Więc niekoniecznie chcę, aby podstawowa pamięć została zwolniona do GC, ponieważ będę musiał ją ponownie przydzielić.
Chris Weber,
to jest to, czego szukałem!)
Timur Fayzrakhmanov
5
Oparty na tytule „Jak wyczyścić plasterek w Go?” jest to zdecydowanie bezpieczniejsza odpowiedź i powinna być akceptowana. Idealną odpowiedzią byłoby połączenie pierwotnie zaakceptowanej odpowiedzi i tej, aby ludzie mogli decydować samodzielnie.
Shadoninja
1
appendrobienie nilkawałka zawsze działało w Go?
alediaferia
@alediaferia od tamtej pory z pewnością 1.0.
Nick Craig-Wood
4

Przyglądałem się temu zagadnieniu trochę dla własnych celów; Miałem kawałek struktur (w tym kilka wskazówek) i chciałem się upewnić, że zrobiłem to dobrze; trafiłem na ten wątek i chciałem podzielić się moimi wynikami.

Aby poćwiczyć, zrobiłem mały plac zabaw: https://play.golang.org/p/9i4gPx3lnY

co odpowiada temu:

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

Uruchomienie tego kodu w niezmienionej postaci pokaże ten sam adres pamięci dla zmiennych „meow” i „meow2” jako taki sam:

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

co, jak sądzę, potwierdza, że ​​struktura jest zbierana jako śmieci. Co dziwne, odkomentowanie komentowanej linii wydruku przyniesie różne adresy pamięci dla miauknięć:

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

Myślę, że może to być spowodowane odroczeniem wydruku w jakiś sposób (?), Ale interesującą ilustracją niektórych zachowań zarządzania pamięcią i jeszcze jednym głosem na:

[]MyStruct = nil
max garvey
źródło
Ładne szczegółowe przykłady. Dzięki!
Dolanor
2
To nie pokazuje, że adresy pamięci meo1 i meow2 są takie same: 0x1030e0c0nie są równe 0x1030e0f0(pierwszy kończy się na c0, drugi na f0).
carbocation
Zgadzam się z @carbocation, te adresy pamięci nie są takie same. Nie twierdzę, że potrafię lepiej wyjaśnić, co się tutaj dzieje, ale to nie jest dla mnie dowodem. Widzę tę samą 8-bajtową rozbieżność w adresach meow2każdego uruchomienia ...
rbrtl