循環變量的優化
如今來介紹編譯器如何識別循環變量的問題。
好比前面各類關於循環的優化中,咱們若是要計算依賴向量,起碼編譯器要可以識別循環變量,
而識別循環變量也是編譯器的一個優化過程。
而一般編譯器所認爲的循環變量可能同咱們所看到的有所不一樣。
一般的循環變量是狹義的,也就是說,若是這個變量在一個循環的任意兩次相鄰的迭代中變換值是常數,
那麼這個變量就是循環變量。最多見的循環變量是以下代碼中:
若是循環體中沒有對變量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代碼中大整數作循環變量的代碼,那麼遇到上面的代碼編譯器
的優化一樣無能爲力了,那麼就須要手工作相似的優化了。
歡迎關注本站公眾號,獲取更多信息