C++中模板元編程原理及速度測試

來源:互聯網
上載者:User

這兩天一直被這個模板元編程給迷住了,覺得它真是一個很好的東西!於是好奇就仔細的研究了下,之前看過幾篇文章大概的意思就是“編可以編程的程式”。聽起來很神奇吧。

 

其基本原理也就是讓編譯器在編譯期間就計算好一些我們需要計算的值。在程式運行期間就不需要再去計算這些值了,從而提高程式的運行效能。當然這樣做會讓程式編譯起來很慢,一般不常用。不過在一些需要的地方我們還是捨得編譯的效率問題的。。

 

先寫個例子看看其中的原理:

#include <iostream><br />using namespace std;</p><p>template< int x, int y ><br />struct ADD<br />{<br /> enum{ Result = ( x > y ) ? x + 20 : y + 100 };<br />};</p><p>int main( void )<br />{<br /> int a = ADD< 2, 3 >::Result;</p><p> system( "pause" );<br /> return 0;<br />}

在上邊的代碼裡,我們看模板結構裡面傳了倆整形的變數。結構體裡面用來產生枚舉值,我們可以先從理論上得到結論是在編譯期間這些枚舉值就會被計算出來。是一個透明的資料。而且最終得到的a應該等於103。我們先運行:

 

輸出結果:103

 

結果沒錯,但是我們看不到到底是不是在編譯期間完成的啊。。怎麼辦呢? 於是在 int a = ADD< 2, 3 >::Result; 執行之前設個斷點,或者按F10直接斷在main函數開頭。我們來查看反組譯碼代碼:

00411B00  push        ebp 
00411B01  mov         ebp,esp
00411B03  sub         esp,0CCh
00411B09  push        ebx 
00411B0A  push        esi 
00411B0B  push        edi 
00411B0C  lea         edi,[ebp-0CCh]
00411B12  mov         ecx,33h
00411B17  mov         eax,0CCCCCCCCh
00411B1C  rep stos    dword ptr [edi]

00411B1E  mov         dword ptr [a],67h

00411B25  push        offset string "pause" (4260C8h)
00411B2A  call        @ILT+565(_system) (41123Ah)
00411B2F  add         esp,4

00411B32  xor         eax,eax

 

上邊是整個main函數的反組譯碼代碼,我們可以看到上邊紅色的那句彙編代碼就是給a 賦枚舉值Result,看直接MOV的是67h = 103! 由此可以證明我們的枚舉值是在編譯期間就計算好了的。。 

 

前面看過一篇文章,是關於用模板元來解迴圈。我們以求一個整形數組所有元素之和為例子寫了一個測試:

 

#include <iostream>

using namespace std;

 

template< int count, class Ty >
class ADD
{
public:
    static int Result( Ty* elem )
    {
        return elem[ 0 ] + ADD< count - 1, Ty >::Result( elem++ );
    }
};

template< class Ty >
class ADD< 1, Ty >
{
public:
    static int Result( Ty* elem )
    {
        return elem[ 0 ];
    }
};

int main( void )
{
    __int64 BegTime = 0;
    __int64 EndTime = 0;
    __int32 sum = 0;

    int a[ 3 ] = { 4, 5, 6 };

    __asm
    {
        rdtsc
        mov dword ptr [ BegTime ], eax
        lea eax, dword ptr [ BegTime ]
        mov dword ptr [ eax + 4 ], edx
    }
   
    ADD< 3, int >::Result( a );

    __asm
    {
        rdtsc
        mov dword ptr [ EndTime ], eax
        lea eax, dword ptr [ EndTime ]
        mov dword ptr [ eax + 4 ], edx
    }

    cout << EndTime - BegTime << endl;

    BegTime = 0;
    EndTime = 0;

    __asm
    {
        rdtsc
        mov dword ptr [ BegTime ], eax
        lea eax, dword ptr [ BegTime ]
        mov dword ptr [ eax + 4 ], edx
    }

    for ( int i = 0; i < 3; ++i )
    {
        sum += a[ i ];
    }

    __asm
    {
        rdtsc
        mov dword ptr [ EndTime ], eax
        lea eax, dword ptr [ EndTime ]
        mov dword ptr [ eax + 4 ], edx
    }

    cout << EndTime - BegTime << endl;

    system( "pause" );
    return 0;
}

 

上邊的程式中紅色部分表示模板元終結模板吧,我是這樣理解的。。名字無所謂啦 - - 它的意思是當我程式執行到模板參數一為1的情況寫就終結調用了。 當然可以參數2或者多個限定。過程是這樣的:

調用順序為: ADD< 3, int >::Result( int* )   =>   ADD< 2, int >::Result( int* )  =>  ADD< 1, int >::Result( int* )相當於轉換成遞迴了 。。。

 

那我們在編譯期間是不是編譯器就已經遞迴調用了Function Compute出結果,我們在運行時只需要取這個值這麼簡單呢?我們斷在ADD< 3, int >::Result( a ); 之前或它上邊,來看看反組譯碼代碼:

 

0041B291  lea         eax,[a]
0041B294  push        eax 
0041B295  call        ADD<3,int>::Result (419A82h)
0041B29A  add         esp,4

 

我們在藍色的地方F11跟進去,

一直跟到這裡:

 

0041BFCE  mov         eax,dword ptr [elem]
0041BFD1  mov         dword ptr [ebp-0C4h],eax
0041BFD7  mov         ecx,dword ptr [elem]
0041BFDA  add         ecx,4
0041BFDD  mov         dword ptr [elem],ecx
0041BFE0  mov         edx,dword ptr [ebp-0C4h]
0041BFE6  push        edx 
0041BFE7  call        ADD<2,int>::Result (4193CFh)
0041BFEC  add         esp,4
0041BFEF  mov         ecx,dword ptr [elem]
0041BFF2  add         eax,dword ptr [ecx]

 

我們再在藍色的地方跟進去,

一直到這裡:

 

0041C6AE  mov         eax,dword ptr [elem]
0041C6B1  mov         dword ptr [ebp-0C4h],eax
0041C6B7  mov         ecx,dword ptr [elem]
0041C6BA  add         ecx,4
0041C6BD  mov         dword ptr [elem],ecx
0041C6C0  mov         edx,dword ptr [ebp-0C4h]
0041C6C6  push        edx 
0041C6C7  call        ADD<1,int>::Result (41A063h)
0041C6CC  add         esp,4
0041C6CF  mov         ecx,dword ptr [elem]
0041C6D2  add         eax,dword ptr [ecx]

 

我們再在藍色的地方跟進去,

一直到這裡:

 

0041CAE0  push        ebp 
0041CAE1  mov         ebp,esp
0041CAE3  sub         esp,0C0h
0041CAE9  push        ebx 
0041CAEA  push        esi 
0041CAEB  push        edi 
0041CAEC  lea         edi,[ebp-0C0h]
0041CAF2  mov         ecx,30h
0041CAF7  mov         eax,0CCCCCCCCh
0041CAFC  rep stos    dword ptr [edi]

0041CAFE  mov         eax,dword ptr [elem]
0041CB01  mov         eax,dword ptr [eax]

 

看到整個過程了吧,這裡可以明白一點就是在上邊的彙編代碼裡面我標註成紅色的數字是在編譯期間計算好了的。我們在定位的時候直接就傳入了模板元在編譯期間計算後的數字。但是在這裡並沒有看到整個調用過程在編譯期間執行。之前看過的文章說會在編譯期間執行也不知道為什麼。我們上邊的測試是可以在運行期間斷下來的並且執行了遞迴調用。

 __asm 
 {
    rdtsc
    mov dword ptr [ BegTime ], eax
    lea eax, dword ptr [ BegTime ]
    mov dword ptr [ eax + 4 ], edx

}

 

這段代碼在前面的博文中已經提到過,是擷取程式運行以來的時間(納秒級)。

這裡測試我們模板元解開迴圈後的速度和迴圈的速度。

程式輸出結果為:

3090周期

130周期

 

這裡的周期除以CPU主頻率得到啟動並執行時間,這裡不用計算就能明顯區分出來了 - -

 

可見解開迴圈只是變成了遞迴,真正起到作用的我認為是在編譯期間計算好了每次遞迴所應該傳入的整形數值。但是遞迴開銷很大,而且很容易堆疊溢位。。所以這個問題還得我們深思。。不過在編譯期間運算一些比如第一個例子計算枚舉或類似的操作時,模板元的好處就得以體現了。。

 

有什麼說的不對的地方望大家指教。。我是菜鳥 - -

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.