datagott > comp.* > comp.programmeren

Jo Vermeulen (19.09.2003, 09:55)
Hoi,

Ik had een vraagje. In een C# project (in het compact .NET framework)
vervang ik soms een afbeelding door een andere afbeelding. Ik heb gezien
dat sommige mensen dan eerst dit doen:

if (image != null)
{
image.Dispose();
image = null;
}

Is dat nodig? Ik dacht dat C# garbage collection had. Of dient het enkel
om C# een "hint" te geven dat dit object verwijderd kan worden?

Als het niet nodig is, hoe zit het dan met de complexiteit? Kan ik deze
code beter niet gebruiken of maakt het niet zo zeer uit qua complexiteit?

Mvg,
rick (19.09.2003, 12:01)
> Ik had een vraagje. In een C# project (in het compact .NET framework)
> vervang ik soms een afbeelding door een andere afbeelding. Ik heb gezien
> dat sommige mensen dan eerst dit doen:
> if (image != null)
> {
> image.Dispose();
> image = null;
> }
> Is dat nodig? Ik dacht dat C# garbage collection had. Of dient het enkel
> om C# een "hint" te geven dat dit object verwijderd kan worden?
> Als het niet nodig is, hoe zit het dan met de complexiteit? Kan ik deze
> code beter niet gebruiken of maakt het niet zo zeer uit qua complexiteit?


Bovenstaande code is (alleen) nuttig als je 'image' niet langer meer
gebruikt. Als je aan 'image' direct een andere afbeelding 'hangt', bv :

image = mijnAndereImage;

dan heeft het geen enkele zin om (eerst) die code uit te voeren,
dat gebeurt dan al automatisch ('onder water').

Maar, zoals gezegd, als je 'image' niet langer meer gebruikt, dan is die
code WEL nuttig, omdat de garbage collector dan 'ziet' dat dit object
opgeruimd kan worden.

Als je dat niet doet en 'image' is een variabele die niet meer 'out of
scope' raakt tijdens het draaien van het programma (het is bv een
class-variabele (member) van een class die voortdurend 'bestaat',
dan zal 'image' geheugen blijven vasthouden omdat de garbage
collector het object niet kan opruimen.
Pieter Philippaerts (19.09.2003, 13:41)
"Jo Vermeulen" <jo> wrote
> Ik had een vraagje. In een C# project (in het compact .NET framework)
> vervang ik soms een afbeelding door een andere afbeelding. Ik heb gezien
> dat sommige mensen dan eerst dit doen:
> Is dat nodig? Ik dacht dat C# garbage collection had. Of dient het enkel
> om C# een "hint" te geven dat dit object verwijderd kan worden?


Alle klassen in de .NET runtime die zogenaamde unmanaged resoruces
encapsuleren hebben ofwel een Dispose ofwel een Close methode.
De reden hiervoor is dat de garbage collector maar af en toe uitgevoerd
wordt en dit kan problemen opleveren met sommige unmanaged resources.

Neem bijvoorbeeld een File.

FileStream fs = File.Open(.., FileShare.None);
fs.Read(...);
fs = null;
// rest van code

Het probleem met bovenstaande code is dat het bestand open blijft totdat de
GC aangeroepen wordt [wat potentieel een hele tijd kan duren]. Dit wil
zeggen dat andere programma's die dat bestand willen openen gewijgerd
worden, omdat je file sharing op None hebt gezet. Volgende code is een stuk
beter:

FileStream fs = File.Open(.., FileShare.None);
fs.Read(...);
fs.Close();
// rest van code

Bovenstaande code gaat het bestand sluiten zodra het niet meer nodig is.

Het is duidelijk dat bovenstaand geval enkel van toepassing is voor
bestanden, maar er zijn nog andere redenen om unmanaged resources zo snel
mogelijk vrij te geven. Zo hebben de oudere Windows platformen [98, ME,
misschien ook NT4] een vrij beperkte set van handles en zo ongeveer elke
unmanaged resource [buiten geheugen] gebruikt zo'n handles. Het is dus
duidelijk dat als je 10000 images opent zonder ze te sluiten dat je image
handles wel eens op kunnen geraken voordat de eerste image handles door de
GC terug vrijgegeven worden.

Een derde reden die in jouw geval van toepassing is is geheugengebruik.
Images kunnen heel wat geheugen innemen en dus is het misschien wel een goed
idee om dat geheugen zo snel mogelijk terug vrij te geven.

MVg,
Pieter Philippaerts
Gerrit Cornelis (19.09.2003, 13:54)
<rick> wrote in message
news:f967
complexiteit?
> Bovenstaande code is (alleen) nuttig als je 'image' niet langer meer
> gebruikt. Als je aan 'image' direct een andere afbeelding 'hangt', bv :
> image = mijnAndereImage;
> dan heeft het geen enkele zin om (eerst) die code uit te voeren,
> dat gebeurt dan al automatisch ('onder water').


zonder veel te kennen van c# denk ik dat het sturen van de Dispose wel nodig
is, of in ieder geval netter is, als je het image object niet meer gaat
gebruiken. Een Image object kan externe resources vasthouden en je weet
nooit zeker wanneer die gaan gereleased worden als je het niet expliciet
doet (garbage collection gebeurt wanneer de run time environment ertoe
beslist, niet wanneer jij het wil). ik heb er geen idee van of de garbage
collector op het moment van memory reclaim ook nog een dispose doet.
misschien wel, maar het is niet netjes van externe resources vast te houden.
en voor hetzelfde geld wordt het stuk geheugen van dat object nooit
gereclaimed.
rick (19.09.2003, 14:56)
>> > Ik had een vraagje. In een C# project (in het compact .NET framework)
>complexiteit?
>zonder veel te kennen van c# denk ik dat het sturen van de Dispose wel nodig
>is, of in ieder geval netter is, als je het image object niet meer gaat
>gebruiken. Een Image object kan externe resources vasthouden en je weet
>nooit zeker wanneer die gaan gereleased worden als je het niet expliciet
>doet (garbage collection gebeurt wanneer de run time environment ertoe
>beslist, niet wanneer jij het wil). ik heb er geen idee van of de garbage
>collector op het moment van memory reclaim ook nog een dispose doet.
>misschien wel, maar het is niet netjes van externe resources vast te houden.
>en voor hetzelfde geld wordt het stuk geheugen van dat object nooit
>gereclaimed.


Dat is toch precies wat ik zei ? 'Bovenstaande code is (alleen) nuttig als
je 'image' niet langer meer gebruikt. '

Bovendien ging ik daarna nog het één en ander wat meer toelichten,
maar net dat stukje tekst heb je in deze reply weggeknipt (...) :

'Maar, zoals gezegd, als je 'image' niet langer meer gebruikt, dan is die
code WEL nuttig, omdat de garbage collector dan 'ziet' dat dit object
opgeruimd kan worden.

Als je dat niet doet en 'image' is een variabele die niet meer 'out of
scope' raakt tijdens het draaien van het programma (het is bv een
class-variabele (member) van een class die voortdurend 'bestaat',
dan zal 'image' geheugen blijven vasthouden omdat de garbage
collector het object niet kan opruimen.'
Gerrit Cornelis (19.09.2003, 15:39)
<rick> wrote in message
news:1b79
[..]
> class-variabele (member) van een class die voortdurend 'bestaat',
> dan zal 'image' geheugen blijven vasthouden omdat de garbage
> collector het object niet kan opruimen.'


Volgens mij ging de vraag over waarom je Dispose() expliciet moet sturen en
niet over het op null zetten van de variabele. De post van Pieter bevat een
veel betere uitleg dan de mijne maar komt op hetzelfde neer. en ik kan mij
natuurlijk vergissen en misschien ging de vraag over de null assignatie aan
de image variabele en dan is die inderdaad onnodig voor de garbage collector
maar wel veel netter want je hebt het Image object net onbruikbaar gemaakt.
bijgevolg maak je het beter ook onbereikbaar.

Gerrit
Ramon Smits (20.09.2003, 00:58)
Pieter Philippaerts wrote:

> Een derde reden die in jouw geval van toepassing is is geheugengebruik.
> Images kunnen heel wat geheugen innemen en dus is het misschien wel een goed
> idee om dat geheugen zo snel mogelijk terug vrij te geven.


De garbage collector gaat pas ruimen nadat er daadwerkelijk het systeem
bijna geen fysiek vrij geheugen meer is of bij een method call op de
garbage collector GC.Collect().

Dit gebeurd omdat dit een kostbare aangelegenheid is.

Tegenwoordig hebben nieuwe computers 512 of 1024MB geheugen. Dat duurt
echt wel even voor dat vol is en heel leuk om dit gedrag ook echt terug
te zien.

De dispose zoals eerder in deze thread is vermeld dient inderdaad voor
het opruimen van unmanaged resources zoals handles.

Dit kan op global scope inderdaad als

if(o!=null)
{
o.Dispose();
o=null;
}

Op localscope kun je in C# ook het using statement gebruiken

using(o = new O())
{

}

Bij het verlaten van de using scope word automatisch de Dispose
aangeroepen. /Ook/ als in de using scope een exception optreed.

Perfect voorbeeld daarvoor is de DataRepeater.

M.v.g.,
Ramon
Serve La (20.09.2003, 10:05)
"Ramon Smits" <exyll> wrote in message
news:514c
> Pieter Philippaerts wrote:
> > Een derde reden die in jouw geval van toepassing is is geheugengebruik.
> > Images kunnen heel wat geheugen innemen en dus is het misschien wel een goed
> > idee om dat geheugen zo snel mogelijk terug vrij te geven.
>> De garbage collector gaat pas ruimen nadat er daadwerkelijk het systeem

> bijna geen fysiek vrij geheugen meer is


Dat vind ik nogal moeilijk te geloven en dat gedrag zie ik ook helemaal niet
in een testprogje van mij.

> Dit gebeurd omdat dit een kostbare aangelegenheid is.


Wat?

> Tegenwoordig hebben nieuwe computers 512 of 1024MB geheugen. Dat duurt
> echt wel even voor dat vol is en heel leuk om dit gedrag ook echt terug
> te zien.


Dat wil nog niet zeggen dat alle programma's ineens asociaal veel geheugen
moeten gebruiken.
Ook met 512mb zit processen elkaar snel in de weg.
Stel, je hebt een tekstverwerker, mailprogramma, ontwikkelomgeving en een
tekenprogramma openstaan die zich allemaal zo gedragen. Dan zit je met je
eeuwig thrashende 512meg 3GHZ te koekeloeren alsof je een P100/32mb hebt.
Andreas Sikkema (20.09.2003, 10:59)
Serve La wrote:

> Stel, je hebt een tekstverwerker, mailprogramma, ontwikkelomgeving en een
> tekenprogramma openstaan die zich allemaal zo gedragen. Dan zit je met je
> eeuwig thrashende 512meg 3GHZ te koekeloeren alsof je een P100/32mb hebt.


Als je het zo bekijkt is een GC dus eng. En alleen maar een speeltje
voor foute programmeurs ;-)
Ramon Smits (20.09.2003, 11:28)
Serve La wrote:

>>De garbage collector gaat pas ruimen nadat er daadwerkelijk het systeem
>>bijna geen fysiek vrij geheugen meer is

> Dat vind ik nogal moeilijk te geloven en dat gedrag zie ik ook helemaal niet
> in een testprogje van mij.


Tsja... low on memory misschien :). Wat doet je test applicatie om dit
na te bootsen?

WinForm applicaties gaan ook garbagecollecten bij een minimize als ik me
niet vergis...

>>Dit gebeurd omdat dit een kostbare aangelegenheid is.

> Wat?


Garbage collecten...

Je 'heap' groeit namelijk enorm. Als de garbage collector gaat ruimen
dan heb je ook brokken geheugen die verplaatst moeten worden. De GC moet
zeg maar de 'heap' gaan defragmenteren. Daarom moet je in je code
eigenlijk nooit GC.Collect() aanroepen.

> Dat wil nog niet zeggen dat alle programma's ineens asociaal veel geheugen
> moeten gebruiken.


Dat zeg ik ook niet :). It's all about design. Waarom een object continu
creeeren en destroyen als je weet dat je die actie per uur 1 miljoen
maal moet doen. Dan is het nuttig dergelijke objecten te cachen. Maar
dat is eigenlijk altijd al geweest.

> Ook met 512mb zit processen elkaar snel in de weg.
> Stel, je hebt een tekstverwerker, mailprogramma, ontwikkelomgeving en een
> tekenprogramma openstaan die zich allemaal zo gedragen. Dan zit je met je
> eeuwig thrashende 512meg 3GHZ te koekeloeren alsof je een P100/32mb hebt.


Inderdaad een bulk aan applicaties die veel geheugen nodig hebben. Maar
dat zijn piek momenten. Je tekstverwerken is niet continu met een
spellingscontrole bezig en je ontwikkelomgeving niet continu aan het
compileren en je tekenprogramma niet continu een batch aan
beeldbewerkingen. Als een applicatie veel geheugen nodig heeft wil dit
niet zeggen dat die applicatie dat geheugen ook continu nodig heeft. Zet
anders maar eens 30 applicaties open met data. Je zult zien dat de
applicatie die de focus heeft gewoon goed performed ondanks dat je
systeemgeheugen toch echt op is.
Marco van de Voort (20.09.2003, 11:52)
In article <3f6c1dd6$0$135$e4fe514c>, Ramon Smits wrote:
> Serve La wrote:
>> Dat wil nog niet zeggen dat alle programma's ineens asociaal veel geheugen
>> moeten gebruiken.

> Dat zeg ik ook niet :). It's all about design. Waarom een object continu
> creeeren en destroyen als je weet dat je die actie per uur 1 miljoen
> maal moet doen. Dan is het nuttig dergelijke objecten te cachen. Maar
> dat is eigenlijk altijd al geweest.


Als ze in een freelist zitten voor een bepaalde grootte, zijn ze al
gecollect. Dus gecachete allocaties tellen niet mee in je applicatie
geheugen gebruik. (tenzij je VM/heapmanager ze niet freed als de memory
low watermark gehaald wordt )

>> Ook met 512mb zit processen elkaar snel in de weg.
>> Stel, je hebt een tekstverwerker, mailprogramma, ontwikkelomgeving en een
>> tekenprogramma openstaan die zich allemaal zo gedragen. Dan zit je met je
>> eeuwig thrashende 512meg 3GHZ te koekeloeren alsof je een P100/32mb hebt.

> Inderdaad een bulk aan applicaties die veel geheugen nodig hebben. Maar
> dat zijn piek momenten.


Gebruik maar eens JBuilder als ontwikkelgebeuren. Je leven is dan een groot
piekmoment met 512MB, nog onafhankelijk van de rest :-)

> Je tekstverwerken is niet continu met een
> spellingscontrole ;bezig en je ontwikkelomgeving niet continu aan het
> compileren en je tekenprogramma niet continu een batch aan
> beeldbewerkingen.


Maar de vertraging die uitswappen kost tijdens gewoon werken (en b.v. eens
in de 10/20 minuten switches) is IMHO ook al fout, dat werkt al niet lekker
meer als ie dan 10 seconden gaat trashen, om even iets in de documentatie
te cut and pasten.

> Als een applicatie veel geheugen nodig heeft wil dit
> niet zeggen dat die applicatie dat geheugen ook continu nodig heeft. Zet
> anders maar eens 30 applicaties open met data. Je zult zien dat de
> applicatie die de focus heeft gewoon goed performed ondanks dat je
> systeemgeheugen toch echt op is.


De applicaties die genoemd werden zijn allemaal interactief. Voor slapende
services kan ik het me nog voorstellen.
Ramon Smits (20.09.2003, 13:53)
Marco van de Voort wrote:

> Als ze in een freelist zitten voor een bepaalde grootte, zijn ze al
> gecollect. Dus gecachete allocaties tellen niet mee in je applicatie
> geheugen gebruik. (tenzij je VM/heapmanager ze niet freed als de memory
> low watermark gehaald wordt )


Wat is een freelist? Een lijst van alle recources waar geen references
vanuit de applicatie meer staan?

Inderdaad... de tresshold bepaald wanneer de garbage collector gaan
handelen. Maar dan moet inderdaad wel die tresshold behaald worden
tijdens het draaien van de betreffende applicatie.

Een cache word nooit vrijgegeven. Dat is nou het mooie van een cache.
Uiteraard telt die WEL mee met je applicatie geheugen gebruik. Aangezien
de objecte in de betreffende cache snel oud worden gaat de .NET GC ze
overslaan omdat deze in een oudere generatie komen. De .NET GC is zo
slim om dan niet bij elke GC cycle die objecten te checken omdat ze
waarschijnlijk nog steeds niet gecollect kunnen worden.

> in de 10/20 minuten switches) is IMHO ook al fout, dat werkt al niet lekker
> meer als ie dan 10 seconden gaat trashen, om even iets in de documentatie
> te cut and pasten.


Inderdaad.. dat is ook vrij irritant. Garbage collecten wil je ook op
die momenten doen zodra de betreffende applicatie idle is. Dat doet de
VM in Java en .NET maar een C++ applicatie heeft dergelijke
functionaliteit vaak niet omdat het een hels karwij is. De gebruiker
merkt vaak deze opschoon acties dan ook vaak helemaal niet in Java en
..NET. Het enige wat de gebruiker merkt is het jit compileren van de code.

Maar zolang systeem geheugen nog riant beschikbaar is is garbage
collecten nog helemaal niet nodig. Dat is wat ik duidelijk probeer te
maken. De .NET VM zal dat dan ook niet snel doen.

> De applicaties die genoemd werden zijn allemaal interactief. Voor slapende
> services kan ik het me nog voorstellen.


Wat kun je je voorstellen? Dat die idle zijn? Interactieve applicatie
zijn ook net zo vaak idle.

M.v.g.,
Ramon
Marco van de Voort (20.09.2003, 14:08)
In article <3f6c3fca$0$59340$e4fe514c>, Ramon Smits wrote:
> Marco van de Voort wrote:
> Wat is een freelist? Een lijst van alle recources waar geen references
> vanuit de applicatie meer staan?


Een lijst met gedealloceerde (GC of handmatig) geheugen bereiken. Meestal
van een bepaalde grootte categorie. Soms zijn ze meervoudig gesorteerd. (op
grote en op volgorde, sorteren op volgorde maakt mergen van aansluitende
blokken makkelijker)

Dit om compacting en merging makkelijker te maken. De freelist zijn dus
gecachte stukjes geheugen, geen echte objecten meer. (al kan dat als
optimalisatie evt in sommige talen toegevoegd worden)

> Inderdaad... de tresshold bepaald wanneer de garbage collector gaan
> handelen. Maar dan moet inderdaad wel die tresshold behaald worden
> tijdens het draaien van de betreffende applicatie.


Mijn punt was dat geheugen dat nog niet geGCed is, niet hergebruikt wordt.
Ik reageerde op die opmerking over "geheugen cachen", maar ik zie nu dat
jij dat misschien "handmatig' (zelf) cachen heb bedoeld, terwijl ik
caching in the memorymanager van de taal bedoelde.

> Inderdaad.. dat is ook vrij irritant. Garbage collecten wil je ook op
> die momenten doen zodra de betreffende applicatie idle is. Dat doet de
> VM in Java en .NET maar een C++ applicatie heeft dergelijke
> functionaliteit vaak niet omdat het een hels karwij is.


Ik weet dat het onder delphi vermoedelijk wel kan, en van C++ ben ik ook
redelijk zeker.
Al valt dat dan onder de non-cooperative languages. waar je dus in de GC
geen gealloceert geheugen kan laten verplaatsen, hetgeen tot fragmentatie.
Maar mark en sweep zijn universeel, en een thread en event driven applicaties
bestaan in die beide talen ook.

> De gebruiker merkt vaak deze opschoon acties dan ook vaak helemaal niet in
> Java en .NET. Het enige wat de gebruiker merkt is het jit compileren van
> de code.


Ik weet dat niet. Er waren vroeger veel problemen mee, maar ik weet niet
of deze opgelost zijn door nieuwere, betere VMs, of door zwaardere machines
met meer geheugen (en dat dan ook relatief tot de last).

> Maar zolang systeem geheugen nog riant beschikbaar is is garbage
> collecten nog helemaal niet nodig. Dat is wat ik duidelijk probeer te
> maken. De .NET VM zal dat dan ook niet snel doen.
>> De applicaties die genoemd werden zijn allemaal interactief. Voor slapende
>> services kan ik het me nog voorstellen.

> Wat kun je je voorstellen? Dat die idle zijn? Interactieve applicatie
> zijn ook net zo vaak idle.


Dat uitswappen niet problematisch is. Voor een applicatie die weinig
gebruikt wordt, maar toch regelmatig (iedere zoveel minuten b.v.) kan
het erg irritant zijn.
Serve La (20.09.2003, 15:01)
"Ramon Smits" <exyll> wrote in message
news:514c
> Serve La wrote:
> >>De garbage collector gaat pas ruimen nadat er daadwerkelijk het systeem
> >>bijna geen fysiek vrij geheugen meer is

> > Dat vind ik nogal moeilijk te geloven en dat gedrag zie ik ook helemaal niet
> > in een testprogje van mij.

> Tsja... low on memory misschien :). Wat doet je test applicatie om dit
> na te bootsen?


in een timer event geheugen alloceren. Ik zie geen vollopen van het geheugen
tot het bijna vol is (gelukkig)

> > Dat wil nog niet zeggen dat alle programma's ineens asociaal veel geheugen
> > moeten gebruiken.

> Dat zeg ik ook niet :). It's all about design. Waarom een object continu
> creeeren en destroyen als je weet dat je die actie per uur 1 miljoen
> maal moet doen. Dan is het nuttig dergelijke objecten te cachen. Maar
> dat is eigenlijk altijd al geweest.


Wat ik bedoelde is dat al die programma's wel ergens geheugen alloceren. Als
een GC zo werkt dat ie op gaat ruimen als het fysieke geheugen vol is heb je
nog steeds een traag systeem ondankds dat je 512/1024mb hebt. Maar zo werkt
de .NET GC ook niet.

> Inderdaad een bulk aan applicaties die veel geheugen nodig hebben. Maar
> dat zijn piek momenten. Je tekstverwerken is niet continu met een
> spellingscontrole bezig en je ontwikkelomgeving niet continu aan het
> compileren en je tekenprogramma niet continu een batch aan
> beeldbewerkingen. Als een applicatie veel geheugen nodig heeft wil dit
> niet zeggen dat die applicatie dat geheugen ook continu nodig heeft. Zet
> anders maar eens 30 applicaties open met data. Je zult zien dat de
> applicatie die de focus heeft gewoon goed performed ondanks dat je
> systeemgeheugen toch echt op is.


Maar dat heeft allemaal niks met GC te maken, meer met virtueel geheugen.
Pieter Philippaerts (20.09.2003, 17:05)
"Ramon Smits" <exyll> wrote
> >>De garbage collector gaat pas ruimen nadat er daadwerkelijk het systeem
> >>bijna geen fysiek vrij geheugen meer is


Eerst en vooral moet je een onderscheid maken tussen de verschillende
garbage collectors van .NET. Zo gebruiken client applicaties een andere GC
dan server applicaties.
De client GC gaat typisch vaken collecten en zichzelf dus vaker uitvoeren.
Het voordeel is dat de gebruik altijd wel wat geheugen vrij heeft om
bijvoorbeeld nieuwe [eventueel unmanaged] applicaties te starten.
De server GC gaat minder vaak collecten [=> higher throughput voor uw server
apps] maar gebruikt dan ook het geheugen veel minder conservatief.

> WinForm applicaties gaan ook garbagecollecten bij een minimize als ik me
> niet vergis...


Neen, het enige wat Windows doet als je een applicatie minimalizeert is de
working set verkleinen, maar dat doet hij bij alle applicaties, managed of
unmanaged. Probeer maar eens met je nieuwslezer: hier bij mij gaat de
working set van Outlook Express van 21Mb naar 1Mb door te minimalizeren.

> > Dat wil nog niet zeggen dat alle programma's ineens asociaal veel geheugen
> > moeten gebruiken.

> Dan is het nuttig dergelijke objecten te cachen. Maar
> dat is eigenlijk altijd al geweest.


Ik ben ook eerder van de mening om geheugen zo snel mogelijk vrij te geven
als dat gaat -- in managed of unmanaged programmas.
Dat caching dat je daar aanhaalt is niet altijd mogelijk -- het lijkt me al
bijvoorbeeld al niet mogelijk in de situatie van de OP waar hij met
tekeningen werkt.

> [..] en je ontwikkelomgeving niet continu aan het
> compileren is [..]


Duidelijk nog niet met VB.NET gewerkt ;-)

> Je zult zien dat de
> applicatie die de focus heeft gewoon goed performed ondanks dat je
> systeemgeheugen toch echt op is.


Kan misschien ook zijn omdat the Windows threading scheduler een groter
thread quantum geeft aan de actieve applicatie.

Mvg,
Pieter Philippaerts

Soortgelijke onderwerpen