Geheugengebruik en optimalisatie
4

Inleiding
Je kent het wel. Je hebt een mooi stukje code geschreven in een mooi, single-use appje.
Je appje is lean/mean en misschien zelfs wel een fighting machine.
Vol verwachting start je je app. Taskmanager erbij… Je schrikt je een ongeluk.
Waarom verbruikt je simpele appje in hemelsnaam x mb geheugen
En waarom loopt het op?

Ten eerste is de tooll waarmee je het geheugenverbruik bekijkt van belang
Met de taskmanager, een veelgebruikte app, bekijk je de gehele working set en is dus eigenlijk niet de juiste tool. Waarom niet?
Als een.Net applicatie opstart, dan ‘vraagt’ het aan de OS een stuk geheugenruimte.
Dat wordt door de applicatie gesegmenteerd in een managed heap, stack en large object heap.
Het totaal over deze drie stukken is waar de Taskmanager over rapporteert, maar dat hoeft helemaal niet in zijn geheel gebruikt te worden door je applicatie. Je applicatie krijgt het geheugen en geeft het geheugen pas vrij als het OS geheugen te kort komt. Hoe optimaliseer je dus je geheugen?

PerfMon
Ten eerste zou je dus een betere tool kunnen gebruiken om je geheugen allocaties te meten.
PerfMon is een goed begin. PerfMon geeft over de verschillende geheugenstukken diverse counters en rapporteert daarover afzonderlijk. De verschillende counters geven een veel beter beeld over het verbruik van de applicatie en geven inzicht in waarvoor het geheugen wordt gebruikt.
Interop
Ten tweede kan je interop code gebruiken om Win32 API’s aan te roepen om je working set size te minimaliseren. (Zie onderstaand voorbeeld) Het gevolg daarvan is dat je working set omhoog gaat terwijl het OS het geheugen uitdeelt en de .Net Runtime dit aan het ‘configureren’ is.
Hiermee wordt het geheugen dat door de JIT Compiler wordt gebruikt vrijgegeven evenals geheugen dat (eventueel) door de Windows Forms Engine wordt gebruikt.

Een voorbeeld wanneer dit toe te passen is bijvoorbeeld in een windows service die gebruik maakt van een FileListener en je de processen die daar mee gepaard gaan wilt optimalizeren. Door middel van Memory.MinimizeFootPrint() aan te roepen bij het begin van de aanroep leeg je iedere keer je geheugen.

Voorbeeld 1.
geheugengebruik1

Collecties
Ten derde kan het gebruik van de juiste data collecties ook grote impact hebben.
Zie onderstaand schema’s voor een snel overzicht.

10 miljoen unieke strings met een key.

geheugengebruik2

Conclusie – Verder
Door het begrip geheugen beter te definieren vorm, je een beter begrip van het onderhoud daarvan.
Het verschil tussen managed heap, stack en large object heap (LOH) is niet voor iedereen even duidelijk. Dit artikel is ook niet de plaats om dat uit te leggen. Daar zijn hele boeken over geschreven.
Verder is er de GarbageCollection.
GC ruimt niet alleen op, maar compact de managed heap ook, een belangrijke optimalisatie. Hiermee wordt niet alleen je geheugen ge-optimaliseerd maar ook je CPU cache. Verder zorg GC ervoor dat alle objecten groter dan bepaalde grootte automatisch in de LOH worden geplaatst. LOH wordt alleen met een volledige GC opgeruimd. Dit zorgt er overigens ook voor dat je geheugen niet gefragmenteerd wordt.

Het gebruik van de juiste patterns (IDisposable bijv), SOLID code implementaties en peer reviews zou moeten lijden tot betere code die ook beter met geheugen omgaat. Als laatste is geheugen optimalisatie iets wat relatief is. Je moet niet verwachten dat een Word achtige applicatie met 500kb toe kan. Maar zo mag je ook verwachten dat een tool die files opschoont geen 5 MB in beslag neemt.

Door het gebruik van de juiste collecties kan je heel wat winnen. Uiteindelijk is het niet alleen dat wat voor een beter geheugen gebruik zorgt. Maar het helpt wel.

Auteur: Arjan Crielaard / Bergler Competence Center © 2015