¿Cómo se limpia un trozo en Go?

125

¿Cuál es la forma adecuada de borrar un segmento en Go?

Esto es lo que he encontrado en los foros de 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))
}

¿Es esto correcto?

Para aclarar, el búfer se borra para que pueda reutilizarse.

Un ejemplo es la función Buffer.Truncate en el paquete de bytes.

Tenga en cuenta que Reset solo llama a Truncate (0). Entonces parece que en este caso la línea 70 evaluaría: 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
fuente
1
Una prueba rápida sobre: play.golang.org/p/6Z-qDQtpbg parece sugerir que funcionará (no cambiará la capacidad pero truncará la duración)
Jason Sperske

Respuestas:

120

Todo depende de cuál sea su definición de 'claro'. Uno de los válidos ciertamente es:

slice = slice[:0]

Pero hay una trampa. Si los elementos de corte son de tipo T:

var slice []T

entonces hacer cumplir len(slice)cero, por el "truco" anterior, no hace ningún elemento de

slice[:cap(slice)]

elegible para recolección de basura. Este podría ser el enfoque óptimo en algunos escenarios. Pero también podría ser una causa de "pérdidas de memoria": memoria no utilizada, pero potencialmente accesible (después de volver a cortar el 'corte') y, por lo tanto, no basura "recolectable".

zzzz
fuente
1
Interesante. ¿Hay alguna otra manera de eliminar todos los elementos de la matriz subyacente de la porción sin dejar la capacidad subyacente sin cambios?
Chris Weber
3
@ChrisWeber: solo itera sobre la matriz subyacente y establece todos los elementos en un nuevo valor
newacct
2
@jnml, quiero reutilizar el segmento (y el almacenamiento de matriz subyacente), por lo que no estoy asignando constantemente un nuevo segmento (con matriz). He editado mi pregunta para aclarar y mostrar un código de ejemplo de la biblioteca estándar.
Chris Weber
1
Soy nuevo en Go. ¿Podría explicar más sobre por qué este puede ser un enfoque óptimo, por favor? Gracias por adelantado.
Satoru
¿Está seguro de que el restablecimiento del tamaño de segmento causa pérdidas de memoria? No puedo reproducirlo
Tommaso Barbugli
197

Establecer el corte en niles la mejor manera de borrar un corte. nillos cortes en marcha se comportan perfectamente bien y establecer el corte en nilliberará la memoria subyacente al recolector de basura.

Ver patio de recreo

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

Huellas dactilares

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

Tenga en cuenta que los sectores pueden alias fácilmente para que dos sectores apunten a la misma memoria subyacente. La configuración de nileliminará ese alias.

Sin embargo, este método cambia la capacidad a cero.

Nick Craig-Wood
fuente
Nick gracias por la respuesta. Por favor, vea mi actualización, lo haría. Estoy limpiando el segmento para su reutilización. Por lo tanto, no necesariamente quiero que la memoria subyacente se libere al GC, ya que tendré que asignarla nuevamente.
Chris Weber
es lo que busqué!)
Timur Fayzrakhmanov
55
Basado en el título "¿Cómo se borra un segmento en Go?" Esta es de lejos la respuesta más segura y debería ser la aceptada. Sin embargo, una respuesta perfecta sería la combinación de la respuesta originalmente aceptada y esta para que las personas puedan decidir por sí mismas.
Shadoninja
1
appendnil¿Ir a un sector siempre ha funcionado en Go?
alediaferia
@alediaferia desde la versión 1.0 sin duda.
Nick Craig-Wood
4

Estaba investigando este tema un poco para mis propios fines; Tenía una porción de estructuras (incluidos algunos punteros) y quería asegurarme de que estaba bien; terminé en este hilo y quería compartir mis resultados.

Para practicar, hice un pequeño patio de recreo: https://play.golang.org/p/9i4gPx3lnY

que evalúa esto:

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

Ejecutar ese código tal cual mostrará la misma dirección de memoria para las variables "miau" y "miau2" como la misma:

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

lo cual creo que confirma que la estructura es basura recolectada. Curiosamente, al descomentar la línea de impresión comentada, se obtendrán diferentes direcciones de memoria para los maullidos:

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

Creo que esto puede deberse a que la impresión se difiere de alguna manera (?), Pero es una ilustración interesante de algún comportamiento de gestión de memoria y un voto más por:

[]MyStruct = nil
max garvey
fuente
Buenos ejemplos detallados. ¡Gracias!
Dolanor
2
Esto no muestra que las direcciones de memoria de meo1 y meow2 sean las mismas: 0x1030e0c0no es igual a 0x1030e0f0(la primera termina en c0, la segunda en f0).
carbocation
Tengo que estar de acuerdo con @carbocation aquí, esas direcciones de memoria no son las mismas. No pretendo poder explicar mejor lo que está sucediendo aquí, pero esto no me sirve de evidencia. Veo la misma discrepancia de 8 bytes en las direcciones de meow2cada corrida ...
rbrtl