Gegevensgevaren in een pijplijnprocessor doen zich voor wanneer er een gegevensafhankelijkheid bestaat tussen instructies, wat betekent dat één instructie het resultaat van een eerdere instructie nodig heeft voordat dat resultaat beschikbaar is. Dit kan de pijpleiding blokkeren, waardoor de efficiëntie afneemt. Verschillende technieken beperken deze gevaren:
1. Gegevens doorsturen (overbruggen):
* Mechanisme: Dit is de meest gebruikelijke en efficiënte methode. Als het resultaat van een instructie in een latere fase van de pijplijn nodig is voor een instructie in een eerdere fase, wordt het resultaat direct doorgestuurd van de latere fase naar de eerdere fase, waarbij de geheugentoegang wordt omzeild.
* Voorbeeld: Laten we zeggen dat instructie I1 naar register R1 schrijft, en instructie I2 leest vanaf R1. Het doorsturen van gegevens zou de door I1 geschreven waarde rechtstreeks naar I2 sturen, waardoor een stalling wordt voorkomen, ook al heeft I1 het schrijven naar het geheugen nog niet voltooid.
* Effectiviteit: Zeer effectief voor het oplossen van RAW-gevaren (Read After Write), waarbij de afhankelijkheid ligt tussen het schrijven van instructies en het vervolgens lezen van hetzelfde register.
2. Stalling (bellen inbrengen):
* Mechanisme: Als het doorsturen van gegevens niet mogelijk is (bijvoorbeeld als de afhankelijkheid te ver uit elkaar ligt in de pijplijn, of als er sprake is van geheugentoegang), wordt de pijplijn geblokkeerd door het invoegen van "bubbels" (geen operationele instructies) totdat de gegevens gereed zijn.
* Voorbeeld: I1 schrijft naar het geheugen en I2 leest vanaf die geheugenlocatie. Het doorsturen van gegevens is niet haalbaar omdat het schrijven van I1 moet zijn voltooid voordat I2 kan lezen. De pijplijn loopt vast totdat I1 het schrijven heeft voltooid.
* Effectiviteit: Eenvoudiger te implementeren dan doorsturen, maar vermindert de doorvoer van pijpleidingen aanzienlijk.
3. Registreren Hernoemen:
* Mechanisme: De compiler of hardware wijst verschillende registers toe aan instructies die mogelijk een gegevensafhankelijkheid hebben. Dit elimineert de gevaren van WAR (Write After Read) en WAW (Write After Write). Als twee instructies bijvoorbeeld hetzelfde register gebruiken, kan de hardware een ervan hernoemen naar een ander register, waardoor het conflict wordt opgelost.
* Voorbeeld: Twee instructies willen naar R1 schrijven. Door het hernoemen van registers wordt aan de tweede instructie een ander tijdelijk register toegewezen, waardoor het WAW-gevaar wordt opgelost.
* Effectiviteit: Zeer effectief in het elimineren van WAR- en WAW-gevaren, maar brengt hardwarecomplexiteit met zich mee. Vaak gebruikt in combinatie met het doorsturen van gegevens.
4. Compileroptimalisaties:
* Mechanisme: Compilers kunnen de code analyseren op gegevensafhankelijkheden en instructies opnieuw ordenen om gevaren te minimaliseren. Dit kan gepaard gaan met het plannen van instructies om instructies te scheiden die van elkaar afhankelijk zijn, waardoor de noodzaak voor uitstellen of doorsturen wordt verminderd.
* Voorbeeld: De compiler kan instructies opnieuw ordenen om instructies die gegevens lezen verder weg te plaatsen van de instructies die die gegevens schrijven, waardoor de pijplijn meer tijd krijgt om te voltooien voordat de afhankelijke instructie deze nodig heeft.
* Effectiviteit: Vermindert de frequentie van gevaren op broncodeniveau, maar de effectiviteit hangt af van de mogelijkheden van de compiler.
De keuze van de mitigatietechniek hangt af van de specifieke architectuur van de processor. Moderne processors gebruiken doorgaans een combinatie van het doorsturen van gegevens, het hernoemen van registers en compileroptimalisaties voor een efficiënte afhandeling van gegevensgevaren. Stalling wordt vaak gebruikt als laatste redmiddel wanneer andere technieken onvoldoende zijn. |