Self modifying code
 
4. Nadelen en problemen

Het gebruik van self modifying code heeft naast een aantal toepassingen ook een aantal belangrijke nadelen. Enkele daarvan hebben te maken met het principe van het veranderen van een programma en enkele met de huidige mogelijkheden en technieken in hardware.

Op het moment wordt het tijdens runtime wijzigen van instructies in geen enkele gebruikelijke hogere programmeertaal ondersteund, het is een techniek die beschouwd wordt als onwenselijk en niet netjes. In veel talen en besturingssystemen zijn er zelfs uitgebreide beveiligingen ingebouwd, zodat het veranderen van instructies niet eenvoudig is.

Tijdens het ontwerpen van software wordt een statisch beeld gevormd van de taken en bewerkingen die een programma moet uitvoeren. Deze bewerkingen worden gedefinieerd voor een beperkte bekende subset van invoer. Het dynamisch aanpassen van de taak of bewerking aan de hand van de situatie is hierbij niet vanzelfsprekend.

Ook het ontwikkelen van een éénduidige en betekenisvolle schrijfwijze van veranderlijke code zal een probleem zijn. Omdat bij het wijzigen van code het beeld van het programma veranderd is het moeilijker om een statisch overzicht op de werking van het programma te vinden. Dit houdt in dat het onoverzichtelijker wordt om zelfwijzigende programma's te maken.

Het afleiden van de werking van een programma dat self modifying code gebruikt uit de code is moeilijker, omdat de werking tijdens het uitvoeren van het programma kan veranderen. Het debuggen van programma's met self modifying code is niet eenvoudig en kan problemen voor de debugger veroorzaken die bijvoorbeeld breakpoints in de code heeft gezet.

Als een programma meerdere threads of processen gebruikt om haar taken uit te voeren, wat moet er dan gedaan worden als één van de threads een deel van de code wijzigt. Problemen ontstaan als code tegelijk uitgevoerd en gewijzigd wordt. Self modifying code is misschien wel een betrekkelijk eenvoudige oplossing voor het concurrency probleem, maar het op bredere schaal toepassen zorgt voor een groot aantal concurrency problemen.

Ook bij het gebruik van parent-child processen die elkaars codesegment delen omwille van snelheid en geheugengebruik ontstaan soortgelijke problemen.

In huidige technieken om processoren sneller te krijgen worden caches, prefetch-queues en pipelines gebruikt. Deze technieken gaan er van uit dat code statisch is. Bij deze technieken worden delen van code bewaard of zelfs deels verwerkt buiten het hoofdgeheugen. Veel van de voorbeelden uit het vorige hoofdstuk werken niet direct op moderne processoren. Na het wijzigen van de code moet de onder andere de cache geflushed worden. Dit brengt een aanzienlijke vertraging met zich mee, niet alleen door het flushen, maar ook door cache-misses later. Als instructies al in een pipeline of prefetch-queue zitten moet bijvoorbeeld een spronginstructie worden uitgevoerd om de veranderingen te laten gelden. Ook dit brengt vertragingen met zich mee.

Veel 'moderne' processoren laten overigens het wijzigen van geheugen wat voor code bestemd is niet toe in 'user-mode'. Toepassing van self modifying code zou dan alleen op kernel-niveau moeten gebeuren. Zie appendix A voor een eenvoudige test gedaan op een aantal populaire systemen.

In multiprocessorsystemen worden de meeste van deze problemen nog een orde groter. Als bijvoorbeeld code door processor x gewijzigd wordt en een andere processor wil deze code uitvoeren, dan moet processor x de cache van alle andere processoren leeg maken om een voorspelbaar eindresultaat te krijgen.

Ook een probleem met het dynamisch genereren of veranderen van code is dat er gedetailleerde informatie over de architectuur nodig is. Dit maakt het maken van programma's met deze techniek erg architectuurafhankelijk en niet erg eenvoudig toepasbaar in een hogere programmeertaal.

Het is ook niet eenvoudig te zeggen wanneer self modifying code sneller is. Executietijden van programma's zijn moeilijk van tevoren te voorspellen doordat processorarchitecturen met caches en pipelines ingewikkelde timingeigenschappen hebben. Self modifying code maakt dat niet eenvoudiger, omdat cache-flush tijden en cache-misses meespelen.

Als codedelen van tevoren door gebruik van self modifying code klein genoeg gemaakt kunnen worden om zijn geheel in de cache te passen kan dit weer een tijdwinst opleveren.

Er zijn ook geen eenvoudige regels om te bepalen wanneer self modifying code sneller is. Eventuele versnellingen hangen erg sterk samen met het op te lossen probleem en de architectuur. Over het algemeen is het zo dat, als het flushen van de cache niet te veel kosten met zich meebrengt en er veel instructies uitgespaard kunnen worden zou self modifying code voordelig kunnen zijn.

Omdat self modifying code geen ondersteuning in bestaande (populaire) hogere programmeertalen en systemen kent, moeilijk is om duidelijk te ontwerpen, minder inzichtelijke werking heeft, concurrencyproblemen kan opleveren, hoge kosten heeft voor cache-flushen, machineafhankelijk is en eventuele versnelling slecht voorspelbaar is, wordt het gebruik ervan niet aantrekkelijker.