生活随笔
收集整理的這篇文章主要介紹了
循环变量的优化
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
2019獨角獸企業重金招聘Python工程師標準>>>
現在來介紹編譯器如何識別循環變量的問題。 比如前面各種關于循環的優化中,我們如果要計算依賴向量,起碼編譯器要能夠識別循環變量, 而識別循環變量也是編譯器的一個優化過程。 而通常編譯器所認為的循環變量可能同我們所看到的有所不同。 通常的循環變量是狹義的,也就是說,如果這個變量在一個循環的任意兩次相鄰的迭代中變換值是常數, 那么這個變量就是循環變量。最常見的循環變量是如下代碼中: for(i=0;i<n;i++){
? ?....
} 復制代碼
如果循環體中沒有對變量i的修改,那么i就必然是一個循環變量。 但是如果循環體中對i做了修改,那么雖然看上去i還像是一個循環變量,但是對于編譯器來說,i已經不是循環變量了,如: for(i=0;i<n;i++){
??if(..){
? ?? ?i++;
??}
??...
} 復制代碼
像上面的循環體,有時候i增加1,有時候i增加2,那么i就不是循環變量了。 而還有一些情況,循環變量人工不容易看出來,但是編譯器確可以判斷出來,如: i=0;
while(...){
? ?j=i+1;
? ?...
? ?k=j+2;
? ?...
? ?i=k;
? ?...
} 復制代碼
像上面的代碼,如果沒有其他對j,k,i的修改,那么這里i,j,k實際上都是循環變量, 其中每次迭代這三個變量都增加了3. 而對于編譯器來說,通常還可以識別一些更加復雜的循環變量,如: i=0;
while(...){
? ?j=i+1;
? ?...
? ?k=j+2;
? ?h=a*k+j;
? ?...
? ?i=k;
? ?u=h-i+b;
? ?...
} 復制代碼
像上面代碼中,編譯器首先可以識別出i,j,k是循環變量,然后編譯器發現h,u是循環變量的線性組合, 所以編譯器可以識別出它們也是循環變量。(其中a,b可以是變量,只要在循環體內部沒有被修改) 比如h每個循環改變的量為3*(a+1),u每個循環改變的量為3*a,編譯器可以通過將上面代碼改變為: i=0;
h=3*a+1;
u=3*a+1+b;
step1=3*(a+1);
step2=3*a;
while(...){
? ?j=i+1;
? ?...
? ?k=j+2;
? ?///h=a*k+j;///刪除原先這里對h的定義
? ?...
? ?i=k;
? ?///u=h-i+b;///刪除這里對u的定義
? ?...
? ?h+=step1;
? ?u+=step2;
} 復制代碼
在經過這個變換以后,在循環體內部, 所有關于循環變量的計算均可以不包含乘法運算,從而比原先代碼應該可以快一些。 同樣,如果在編譯器優化比較后面的部分,通常,對于數組的訪問都已經被展開, 如代碼 ??for(i=0;i<n;i++){
? ?? ? a[i] =....;
??} 復制代碼
可能被展開成: for(i=0;i<n;i++){
? ???p=a+i*S; ///這里S是常數,代表數組a中每個元素在內存中占用的空間大小
? ???*p=...;
} 復制代碼
那么對于編譯器來說,指針p也是一個循環變量,所以代碼可以被轉化為 p=a;
for(i=0;i<n;i++){
? ???*p=...;
? ???p=p+S;
} 復制代碼
變化以后同樣計算地址中的乘法運算被消除了。 我看到郭給出鏈接中一篇英文文章中介紹到對于數組,最好讓每個元素數據的大小是2的冪,這樣,計算每個元素的地址時候, 乘法就可以被移位替換掉,從而提高了速度。但是,如果那樣的數組通常都是被通過循環變量訪問的,我們可以看出來,完全沒有 必要做那樣的優化(實際上那樣可能會消耗更多的內存空間,從而性能更加差). 此外,有一些比較優秀的程序員,他們知道計算機計算移位比乘法運算快,所以對于下面的代碼 for(i=0;i<n;i++){
? ?a[2*i]=...;
? ?...
} 復制代碼
他們可能寫成了 for(i=0;i<n;i++){
? ?a[i<<1]=...;
? ?...
} 復制代碼
其實,對于編譯器來說,反而前面的代碼更加容易優化,因為編譯器可以非常容易識別出2*i是一個循環變量,從而我們可以計算依賴向量, 做一些前面提到過的如么模變換,仿射變換之類的優化。反而對于后面的代碼,由于通常編譯器是不會將移位運算轉化為乘法運算的,所以 通常的編譯器反而無法知道后面的i<<1也是一個循環變量,從而阻止了進一步優化的可能。 此外,部分編譯器還會對一些循環變量之間的相乘做優化(比如Open64),比如代碼: i=0;
while(...){
? ?j=i+1;
? ?...
? ?k=j+2;
? ?h=a*k+j;
? ?...
? ?i=k;
? ?u=h-i+b;
? ?...
? ?sum+=h*u;
} 復制代碼
在編譯器分析出h和u都是循環變量以后,編譯器就可以對h*u做進一步優化 我們知道 h=h0+i*step1,u=u0+i*step2; 所以h*u=h0*u0+(h0*step2+u0*step1)*i+i*i*step1*step2 分別對于i和i+1計算上面的表達式并相減,我們可以得到對于第i次迭代,h*u的變換值是 ? ?h0*step2+u0*step1+step1*step2+i*2*step1*step2; 所以我們知道,上面代碼于是可以優化成: i=0;
h=3*a+1;
u=3*a+1+b;
step1=3*(a+1);
step2=3*a;
hu=h*u;
ddhu=2*step1*step2;
dhu=h0*step2+u0*step1+step1*step2+ddhu*i;
while(...){
? ?j=i+1;
? ?...
? ?k=j+2;
? ?///h=a*k+j;///刪除原先這里對h的定義
? ?...
? ?i=k;
? ?///u=h-i+b;///刪除這里對u的定義
? ?...
? ?h+=step1;
? ?u+=step2;
? ?sum+=hu;
? ?hu+=dhu;
? ?dhu+=ddhu;? ?
} 復制代碼
從而計算循環體內計算h*u的乘法運算由兩次加法運算代替,從而提高了速度。 同樣道理,對于三個循環變量的相乘,從理論上,我們同樣可以轉化為若干次加法運算。 不過據我所知,并沒有編譯器真正這樣去做,畢竟實際中,這樣代碼的例子會非常少見。 當然,如果換成用郭的HugeCalc代碼中大整數做循環變量的代碼,那么遇到上面的代碼編譯器 的優化同樣無能為力了,那么就需要手工做類似的優化了。
轉載于:https://my.oschina.net/u/218425/blog/51947
總結
以上是生活随笔為你收集整理的循环变量的优化的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。