Konstant vikning

Konstant vikning och konstant utbredning är relaterade kompilatoroptimeringar som används av många moderna kompilatorer . En avancerad form av konstant fortplantning känd som sparse villkorad konstant propagation kan mer exakt sprida konstanter och samtidigt ta bort död kod .

Konstant vikning

Konstant vikning är processen att känna igen och utvärdera konstanta uttryck vid kompilering istället för att beräkna dem vid körning. Termer i konstanta uttryck är vanligtvis enkla literaler, som heltalsliteralen 2 , men de kan också vara variabler vars värden är kända vid kompileringstillfället. Tänk på uttalandet:

         i  =  320  *  200  *  32  ; 

De flesta kompilatorer skulle faktiskt inte generera två multipliceringsinstruktioner och ett lager för detta uttalande. Istället identifierar de konstruktioner som dessa och ersätter de beräknade värdena vid kompilering (i det här fallet 2 048 000 ).

Konstant vikning kan använda sig av aritmetiska identiteter. Om x är numeriskt är värdet på 0 * x noll även om kompilatorn inte känner till värdet på x (observera att detta inte är giltigt för IEEE-floats eftersom x kan vara Infinity eller NaN . Vissa miljöer som gynnar prestanda, t.ex. eftersom GLSL shaders tillåter detta för konstanter, som ibland kan orsaka buggar).

Konstant vikning kan gälla mer än bara siffror. Sammanfogning av strängliteraler och konstantsträngar kan vikas konstant. Kod som "abc" + "def" kan ersättas med "abcdef" .

Konstant vikning och korskompilering

När man implementerar en korskompilator måste man se till att beteendet hos de aritmetiska operationerna på värdarkitekturen matchar det på målarkitekturen, eftersom det annars kommer att förändra programmets beteende genom att möjliggöra konstant vikning. Detta är särskilt viktigt i fallet med flyttalsoperationer , vars exakta genomförande kan variera kraftigt.

Konstant förökning

Konstant fortplantning är processen att ersätta värdena för kända konstanter i uttryck vid kompilering. Sådana konstanter inkluderar de som definierats ovan, såväl som inneboende funktioner som tillämpas på konstanta värden. Tänk på följande pseudokod:

     
         
          int  x  =  14  ;  int  y  =  7  -  x  /  2  ;  returnera  y  *  (  28  /  x  +  2  ); 

Förökning av x ger:

     
         
          int  x  =  14  ;  int  y  =  7-14  /  2  ;  _  _  return  y  *  (  28  /  14  +  2  ); 

Att fortsätta sprida ger följande (vilket sannolikt skulle optimeras ytterligare genom eliminering av död kod av både x och y.)

     
     0
   0 int  x  =  14  ;  int  y  =  ;  återvända  ; 

Konstant spridning implementeras i kompilatorer med hjälp av analysresultat för att nå definition . Om alla variablers definitioner är samma tilldelning som tilldelar variabeln samma konstant, så har variabeln ett konstant värde och kan ersättas med konstanten.

Konstant spridning kan också orsaka att villkorliga grenar förenklas till ett eller flera ovillkorliga uttalanden, när det villkorliga uttrycket kan utvärderas till sant eller falskt vid kompileringstidpunkten för att bestämma det enda möjliga resultatet.

Optimeringarna i aktion

Konstant vikning och fortplantning används vanligtvis tillsammans för att uppnå många förenklingar och minskningar, genom att interfoliera dem iterativt tills inga fler förändringar inträffar. Tänk på den här ooptimerade pseudokoden som returnerar ett slumptal :

     
         
   

      
      
         
  
        int  a  =  30  ;  int  b  =  9-  (  a  /  5  )  ;  int  c  ;  c  =  b  *  4  ;  om  (  c  >  10  )  {  c  =  c  -  10  ;  }  returnera  c  *  (  60  /  a  ); 

Att tillämpa konstant förökning en gång, följt av konstant vikning, ger:

     
     
   

      
      
         
  
      int  a  =  30  ;  int  b  =  3  ;  int  c  ;  c  =  b  *  4  ;  om  (  c  >  10  )  {  c  =  c  -  10  ;  }  returnera  c  *  2  ; 

Att upprepa båda stegen två gånger resulterar i:

     
     
   

    
    
       
  
      int  a  =  30  ;  int  b  =  3  ;  int  c  ;  c  =  12  ;  if  (  sant  )  {  c  =  2  ;  }  returnera  c  *  2  ; 

Eftersom a och b har förenklats till konstanter och deras värden ersatts överallt där de förekom, tillämpar kompilatorn nu eliminering av dödkod för att kassera dem, vilket reducerar koden ytterligare:

   
    

    
       
  
      int  c  ;  c  =  12  ;  if  (  sant  )  {  c  =  2  ;  }  returnera  c  *  2  ; 

I ovanstående kod kan det istället för sant vara 1 eller någon annan boolesk konstruktion beroende på kompilatorns ramverk. Med traditionell konstant spridning får vi bara så mycket optimering. Det kan inte ändra strukturen i programmet.

Det finns en annan liknande optimering, kallad sparse conditional constant propagation , som väljer lämplig gren på basis av if-condition . Kompilatorn kan nu upptäcka att if -satsen alltid kommer att utvärderas till true , c själv kan elimineras, vilket krymper koden ytterligare:

    retur  4  ; 

Om denna pseudokod utgjorde kroppen av en funktion, skulle kompilatorn ytterligare kunna dra fördel av kunskapen att den utvärderar till det konstanta heltal 4 för att eliminera onödiga anrop till funktionen, vilket ger ytterligare prestandavinster.

Se även

Vidare läsning