C++中模板元编程原理及速度测试

 这两天一直被这个模板元编程给迷住了,觉得它真是一个很好的东西!于是好奇就仔细的研究了下,之前看过几篇文章大概的意思就是“编可以编程的程序”。听起来很神奇吧。

 

其基本原理也就是让编译器在编译期间就计算好一些我们需要计算的值。在程序运行期间就不需要再去计算这些值了,从而提高程序的运行性能。当然这样做会让程序编译起来很慢,一般不常用。不过在一些需要的地方我们还是舍得编译的效率问题的。。

 

先写个例子看看其中的原理:

 

  
  
  
  
  1. #include <iostream>   
  2. using namespace std;   
  3.    
  4. templateint x, int y >   
  5. struct ADD   
  6. {   
  7.     enum{ Result = ( x > y ) ? x + 20 : y + 100 };   
  8. };   
  9.    
  10. int main( void )   
  11. {   
  12.     int a = ADD< 2, 3 >::Result;   
  13.    
  14.     system( "pause" );   
  15.     return 0;   
  16. }   

在上边的代码里,我们看模板结构里面传了俩整形的变量。结构体里面用来生成枚举值,我们可以先从理论上得到结论是在编译期间这些枚举值就会被计算出来。是一个透明的数据。而且最终得到的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* )相当于转换成递归了 。。。

 

那我们在编译期间是不是编译器就已经递归调用了函数计算出结果,我们在运行时只需要取这个值这么简单呢?我们断在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主频率得到运行的时间,这里不用计算就能明显区分出来了 - -

 

可见解开循环只是变成了递归,真正起到作用的我认为是在编译期间计算好了每次递归所应该传入的整形数值。但是递归开销很大,而且很容易堆栈溢出。。所以这个问题还得我们深思。。不过在编译期间运算一些比如第一个例子计算枚举或类似的操作时,模板元的好处就得以体现了。。

 

有什么说的不对的地方望大家指教。。我是菜鸟 - -

你可能感兴趣的:(职场,c/c++,休闲,C/C++语言)