web-dev-qa-db-fra.com

Go Memory Consommation Management

Je suis nouveau pour essayer de comprendre comment il gère la consommation de mémoire.

J'ai des problèmes avec la mémoire dans l'un de mes projets de test. Je ne comprends pas pourquoi aller utiliser de plus en plus de mémoire (ne le libérant jamais) lorsque mon programme fonctionne pendant une longue période.

Je suis en cours d'exécution dans le cas de test ci-dessous. Après la première allocation, le programme utilise près de 350 Mo de mémoire (selon ActivityMonitor). Ensuite, j'essaie de le libérer et l'ActivityMonitor montre que la consommation de mémoire double. Pourquoi?

J'exécute ce code sur OS X en utilisant Go 1.0.3.

Quel est le problème avec ce code? Et quelle est la bonne façon de gérer de grandes variables dans les programmes Go?

J'ai eu un autre problème associé à la gestion de la mémoire lors de la mise en œuvre d'un algorithme qui utilise beaucoup de temps et de mémoire; Après avoir fonctionné pendant un certain temps, il jette une exception "hors de mémoire".

package main

import ("fmt" 
"time"
)

func main() {
  fmt.Println("getting memory")
  tmp := make([]uint32, 100000000)
  for kk, _ := range tmp {
    tmp[kk] = 0
  }
  time.Sleep(5 * time.Second)
  fmt.Println("returning memory")
  tmp = make([]uint32, 1)
  tmp = nil
  time.Sleep(5 * time.Second)
  fmt.Println("getting memory")
  tmp = make([]uint32, 100000000)
  for kk, _ := range tmp {
    tmp[kk] = 0
  }
  time.Sleep(5 * time.Second)
  fmt.Println("returning memory")
  tmp = make([]uint32, 1)
  tmp = nil
  time.Sleep(5 * time.Second)  
  return
}
25
duganets

Actuellement, Go utilise un Collecteur de déchets Mark-and-balayage , qui ne définit en général pas lorsque l'objet est jeté.

Cependant, si vous regardez de près, il y a une routine Go appelée sysmon qui fonctionne essentiellement tant que votre programme fait et appelle le GC périodiquement:

// forcegcperiod is the maximum time in nanoseconds between garbage
// collections. If we go this long without a garbage collection, one
// is forced to run.
//
// This is a variable for testing purposes. It normally doesn't change.
var forcegcperiod int64 = 2 * 60 * 1e9

(...)

// If a heap span goes unused for 5 minutes after a garbage collection,
// we hand it back to the operating system.
scavengelimit := int64(5 * 60 * 1e9)

forcegcperiod Détermine la période après laquelle le GC est appelé par la force. scavengelimit Détermine lorsque des effectifs sont renvoyés au système d'exploitation. Les effectifs sont un certain nombre de pages de mémoire qui peuvent contenir plusieurs objets. Ils sont conservés pour scavengelimit temps et sont libérés si aucun objet n'est sur eux et scavengelimit est dépassé.

Plus bas dans le code, vous pouvez voir qu'il existe une option de trace. Vous pouvez utiliser ceci pour voir, chaque fois que le sautain pense qu'il a besoin de nettoyer:

$ GOGCTRACE=1 go run gc.go
gc1(1): 0+0+0 ms 0 -> 0 MB 423 -> 350 (424-74) objects 0 handoff
gc2(1): 0+0+0 ms 1 -> 0 MB 2664 -> 1437 (2880-1443) objects 0 handoff
gc3(1): 0+0+0 ms 1 -> 0 MB 4117 -> 2213 (5712-3499) objects 0 handoff
gc4(1): 0+0+0 ms 2 -> 1 MB 3128 -> 2257 (6761-4504) objects 0 handoff
gc5(1): 0+0+0 ms 2 -> 0 MB 8892 -> 2531 (13734-11203) objects 0 handoff
gc6(1): 0+0+0 ms 1 -> 1 MB 8715 -> 2689 (20173-17484) objects 0 handoff
gc7(1): 0+0+0 ms 2 -> 1 MB 5231 -> 2406 (22878-20472) objects 0 handoff
gc1(1): 0+0+0 ms 0 -> 0 MB 172 -> 137 (173-36) objects 0 handoff
getting memory
gc2(1): 0+0+0 ms 381 -> 381 MB 203 -> 202 (248-46) objects 0 handoff
returning memory
getting memory
returning memory

Comme vous pouvez le constater, aucune invocation de GC n'est faite entre obtenir et revenir. Toutefois, si vous modifiez le délai de 5 secondes à 3 minutes (plus de 2 minutes de forcegcperiod), les objets sont supprimés par le GC:

returning memory
scvg0: inuse: 1, idle: 1, sys: 3, released: 0, consumed: 3 (MB)
scvg0: inuse: 381, idle: 0, sys: 382, released: 0, consumed: 382 (MB)
scvg1: inuse: 1, idle: 1, sys: 3, released: 0, consumed: 3 (MB)
scvg1: inuse: 381, idle: 0, sys: 382, released: 0, consumed: 382 (MB)
gc9(1): 1+0+0 ms 1 -> 1 MB 4485 -> 2562 (26531-23969) objects 0 handoff
gc10(1): 1+0+0 ms 1 -> 1 MB 2563 -> 2561 (26532-23971) objects 0 handoff
scvg2: GC forced // forcegc (2 minutes) exceeded
scvg2: inuse: 1, idle: 1, sys: 3, released: 0, consumed: 3 (MB)
gc3(1): 0+0+0 ms 381 -> 381 MB 206 -> 206 (252-46) objects 0 handoff
scvg2: GC forced
scvg2: inuse: 381, idle: 0, sys: 382, released: 0, consumed: 382 (MB)
getting memory

La mémoire n'est toujours pas libérée, mais le GC a marqué la région de la mémoire comme inutilisée. La liberté commencera lorsque la portée utilisée n'est pas utilisée et plus âgée que limit. Du code du trésor:

if(s->unusedsince != 0 && (now - s->unusedsince) > limit) {
    // ...
    runtime·SysUnused((void*)(s->start << PageShift), s->npages << PageShift);
}

Ce comportement peut bien sûr changer au fil du temps, mais j'espère que vous avez maintenant un peu une sensation lorsque des objets sont jetés par la force et quand ce n'est pas le cas.

Comme indiqué par Zupa, la libération d'objets peut ne pas renvoyer la mémoire au système d'exploitation, donc sur certains systèmes que vous ne pouvez pas voir un changement d'utilisation de la mémoire. Cela semble être le cas pour le plan 9 et les fenêtres selon ce fil sur Golang-Nuts .

35
nemo

Pour finalement (FORCE), collectez une mémoire inutilisée, vous devez appeler runtime.GC() .

variable = nil Peut rendre les choses inaccessibles et ainsi éligibles à la collecte, mais elle ne libère rien.

15
zzzz