[ruby-de] GC.start tut wirklich nichts!

Norbert Melzer timmelzer at gmail.com
Mi Jun 28 17:04:38 JST 2017


Die Optimisten <inform at die-optimisten.net> schrieb am Mi., 28. Juni 2017 um
08:58 Uhr:

> Hallo,
> nur leider gibt Ruby auch intern den Speicher   nach der 1.Hälfte ( "2x
> untereinander") nicht frei,
> denn am Schluß wird der doppelte Speicher verbraucht!
>

Wie misst du das? Und auf was für eine Speicherart beziehst du dich (s.
nächster Abschnitt)


> Der Programmierer weiß (hoffentlich besser als der GC) wann der Speicher
> freigegeben werden sollte, (und vielleicht für andere Prozesse benötigt
> wird).


Nein, weiß er nicht. Deswegen wurden Garbage Collectoren überhaupt erst
entwickelt. Wenn du die Kontrolle über den Speicher zurück haben möchtest,
dann benutze bitte C, C++ oder Rust.


> Daher sollte der GC auch den Prozeß-Speicher freigeben (und dem
> PC dann das Swappen ersparen)


Bitte beachte, dass es mindestens 2 Arten von "Prozess-Speicher" gibt, wie
du ihn hier bezeichnest. Zum einen ist da der angefragte Speicher, in Linux
gerne als "VIRT" bezeichnet. Der Prozess hat irgendwann mal beim
Betriebssystem angefragt ob er Speicher haben darf und das Betriebssystem
hat "Ja" gesagt. Moderne Betriebssysteme tun das. Egal wie utopisch die
Anfrage ist. Dieses nutzt zum Beispiel die Haskell-Runtime und fragt für
jeden Prozess einfach mal nen ganzes Terabyte an.

Der auf diese Art und weise vorwegbeantragte Speicher kann dann vom Program
genutzt werden und das Betriebssystem stellt dann beim Zugriff auf diesen
Speicher diesen dann erst "real" bereit, das Programm bemerkt davon gar
nichts. Dieser Bereich wird unter Linuxoiden Systemen häufig als "RES"
bezeichnet. Ist dieser Speicher einmal vom OS an den Prozess zugewiesen,
kann das OS ihn nur noch durch das töten des Prozesses zurück gewinnen. Das
OS versucht natürlich das zu vermeiden.

Ob und wie dieser Speicher wieder an das OS zurückgegeben wird liegt
alleine in der Hand des Prozesses. Im Falle der Ruby-Laufzeit-Umgebung wird
dieses durch den Ruby-Interpreter geregelt. Der Garbage-Collector hat damit
nichts zutun. Er gibt lediglich durch Ruby-Objekte belgten Speicher für
neue Ruby-Objekte frei.

Vereinfacht gesagt, setzt er einfach nur Einträge in einem C-Array auf
`NULL`, verkleinert dieses Array aber nicht.

Und auch wenn gerade viele Lücken vorhanden sind, werden neue Objekte in
der Regel erst einmal hinten an gestellt um Speicherfragmentation zu
vermeiden, abgesehen davon ist das vom Bookkeeping her tausend mal
einfacher, als nach dem erhöhen des Zeigers auf den nächsten Platz immer
erst gucken zu müssen ob der Platz wirklich frei ist.

Speicher wird erst dann wiederverwertet, wenn entweder die
Ruby-Laufzeit-Umgebung an ein irgendwo hinterlegtes/konfiguriertes Maximum
stößt ODER bis das Betriebssystem beim Speicherzugriff meldet, "Nein, ich
kann dir nicht mehr zuweisen, bitte schau, dass du mit dem Auskommst was du
hast".


> [kann ich unkompliziert abfragen, ob
> bestimmte Vars im Ram oder Swap liegen?]
>

Nein. Aus Sicht eines Prozesses gibt es keinen Swap, es gibt nur "seinen"
Speicher. Davon abgesehen, es liegen keine Variablen im Swap, sondern
Speicherbereiche. Das Betriebssystem kann immer nur ganze Bereiche in den
Swap legen, nicht teile davon.


> Ich denke ein free ist im Vergleich zur Arbeit vom  GC eine Kleinigkeit.
>

Allerdings hast du in Ruby kein "free". Wie weiter oben bereits angemerkt
gibt es Sprachen die dir Speicherkontrolle ermöglichen und es gibt Sprachen
die es nicht tun. Ruby erlaubt es nicht. Ruby gewährt dir die Kontrolle
über den GC **nicht** um Speicher an das Betriebssystem zurück zu geben,
sondern ausschließlich um vor dem Eintritt in einen Zeitkritischen
Abschnitt ungeplante Unterbrechungen durch den GC zu __vermeiden__, nicht
um sie auszuschließen!


> Das Betriebssystem entscheidet dann (besser!), was geswappt wird (als
> wenn der Ruby viel ungenutztes Ram nicht freigibt)
>

Das Betriebssystem wird keinen Speicher swappen der aktiv genutzt wird.
Wenn Ruby Speicher für sich beansprucht diesen aber nicht nutzt, das
Betriebssystem aber gerade RAM für einen anderen Prozess benötigt, dann
wird natürlich der durch Ruby ewig nicht angetastete Bereich ausgelagert
und nicht der Speicher für den neuen Prozess der gerne arbeiten würde.

Swap ist ein implementierungsdetail der Speicherverwaltung des
Betriebssystems mit dem du dich als Entwickler eines Programmes nicht aus
einanderzusetzen brauchst, es sei den du schreibst Treiber oder
Kernelkomponenten, das wiederum tut man nicht in einer Sprache wie Ruby.

Und nun zu deinem Anliegen. Bei mir arbeitet der GC eindeutig:

```
$ cat foo.rb
require "pp"

pp GC.stat

arr = []
(1..1000000).map { arr << ["1"]}
arr = nil

pp GC.stat
GC.start
pp GC.stat
sleep 5
pp GC.stat


$ ruby foo.rb
{:count=>6,
 :heap_allocated_pages=>74,
 :heap_sorted_length=>75,
 :heap_allocatable_pages=>0,
 :heap_available_slots=>30157,
 :heap_live_slots=>29891,
 :heap_free_slots=>266,
 :heap_final_slots=>0,
 :heap_marked_slots=>13423,
 :heap_swept_slots=>3236,
 :heap_eden_pages=>74,
 :heap_tomb_pages=>0,
 :total_allocated_pages=>74,
 :total_freed_pages=>0,
 :total_allocated_objects=>65962,
 :total_freed_objects=>36071,
 :malloc_increase_bytes=>121680,
 :malloc_increase_bytes_limit=>16777216,
 :minor_gc_count=>4,
 :major_gc_count=>2,
 :remembered_wb_unprotected_objects=>185,
 :remembered_wb_unprotected_objects_limit=>292,
 :old_objects=>11180,
 :old_objects_limit=>13578,
 :oldmalloc_increase_bytes=>513882,
 :oldmalloc_increase_bytes_limit=>16777216}
{:count=>18,
 :heap_allocated_pages=>4938,
 :heap_sorted_length=>7952,
 :heap_allocatable_pages=>3012,
 :heap_available_slots=>2012715,
 :heap_live_slots=>2012463,
 :heap_free_slots=>252,
 :heap_final_slots=>0,
 :heap_marked_slots=>1801579,
 :heap_swept_slots=>1440648,
 :heap_eden_pages=>4938,
 :heap_tomb_pages=>0,
 :total_allocated_pages=>4940,
 :total_freed_pages=>2,
 :total_allocated_objects=>2066750,
 :total_freed_objects=>54287,
 :malloc_increase_bytes=>7758472,
 :malloc_increase_bytes_limit=>16777216,
 :minor_gc_count=>12,
 :major_gc_count=>6,
 :remembered_wb_unprotected_objects=>192,
 :remembered_wb_unprotected_objects_limit=>384,
 :old_objects=>1801302,
 :old_objects_limit=>3602604,
 :oldmalloc_increase_bytes=>12930536,
 :oldmalloc_increase_bytes_limit=>16777216}
{:count=>19,
 :heap_allocated_pages=>1290,
 :heap_sorted_length=>7952,
 :heap_allocatable_pages=>3010,
 :heap_available_slots=>525806,
 :heap_live_slots=>12386,
 :heap_free_slots=>513420,
 :heap_final_slots=>0,
 :heap_marked_slots=>12384,
 :heap_swept_slots=>1741501,
 :heap_eden_pages=>1290,
 :heap_tomb_pages=>0,
 :total_allocated_pages=>4942,
 :total_freed_pages=>3652,
 :total_allocated_objects=>2067517,
 :total_freed_objects=>2055131,
 :malloc_increase_bytes=>952,
 :malloc_increase_bytes_limit=>16777216,
 :minor_gc_count=>12,
 :major_gc_count=>7,
 :remembered_wb_unprotected_objects=>191,
 :remembered_wb_unprotected_objects_limit=>382,
 :old_objects=>12108,
 :old_objects_limit=>24216,
 :oldmalloc_increase_bytes=>1336,
 :oldmalloc_increase_bytes_limit=>16777216}
{:count=>19,
 :heap_allocated_pages=>1290,
 :heap_sorted_length=>7952,
 :heap_allocatable_pages=>3010,
 :heap_available_slots=>525806,
 :heap_live_slots=>13154,
 :heap_free_slots=>512652,
 :heap_final_slots=>0,
 :heap_marked_slots=>12384,
 :heap_swept_slots=>1741501,
 :heap_eden_pages=>1290,
 :heap_tomb_pages=>0,
 :total_allocated_pages=>4942,
 :total_freed_pages=>3652,
 :total_allocated_objects=>2068285,
 :total_freed_objects=>2055131,
 :malloc_increase_bytes=>14630,
 :malloc_increase_bytes_limit=>16777216,
 :minor_gc_count=>12,
 :major_gc_count=>7,
 :remembered_wb_unprotected_objects=>191,
 :remembered_wb_unprotected_objects_limit=>382,
 :old_objects=>12108,
 :old_objects_limit=>24216,
 :oldmalloc_increase_bytes=>15014,
 :oldmalloc_increase_bytes_limit=>16777216}
```


> fG Andreas


Bitte verzeihe meine Ignorranz, aber was ist "fG"?

Bye
  Norbert


Mehr Informationen über die Mailingliste ruby-de