2440|7

7815

帖子

56

TA的资源

裸片初长成(中级)

楼主
 

只为uC而生,uS成长历程3 [复制链接]

在决定了继续使用函数回调的机制实现 uSer和Apper之间通信的方法,在继续实现以前,我们先做一件很重要的事情。

我们来进行一次简单的 时间开销 测试。
因为使用的难易可以通过改进,但是时间开销却是一个硬性指标。

我们已经大致预见到,这样的函数回调数量不会少。(想想,i2c等时序都需要IO实现,所以这样的回调是非常多的,所以它的时间开销会产生致命性影响,不可不慎查。)

为了方便起见,我们采用一种不需要任何其他条件的方法,在debug模式下,通过比较 普通函数调用 和 通过函数指针调用函数 所用的时间差别。
因为我们只关心相对比例,所以我们不需要一个多么精确地计时器。

为此,一个最简单的实现方法是一个 定时中断。

我们也不采用复杂的uSer和Apper这种复杂的调用机制,而是最一般的做法。
以下是code:
  1. void TestFun(void)
  2. {
  3. }

  4. extern U16 Times;
  5. void main(void)
  6. {  
  7.     static U16 i;
  8.     void (*pTest)(void) = NULL;
  9.    
  10.     Timer_Initial();
  11.    
  12.     pTest = TestFun;
  13.    
  14.     while(1)
  15.     {
  16.         Times = 0;
  17.         for(i = 0;i < 5000;i++)
  18.             //TestFun();
  19.             (*pTest)();
  20.         i = 0;  // for breakpoint here.
  21.     }
复制代码
  1. U16 Times = 0;

  2. #pragma vector=0xF
  3. __interrupt void TIM2_UPD_OVF_BRK_IRQHandler(void)
  4. {  
  5.    TIM2_ClearITPendingBit(TIM2_IT_UPDATE);
  6.    
  7.    Times++;
  8. }
复制代码
上面是main函数,下面是 定时中断,整个函数非常纯粹。
在debug时,把断点设在 i = 0;的语句。

测试发现,直接调用函数,断点时Timer值为17;
而通过函数指针调用,断点时Timer值为19;

这个比例说起来不算太大,但也不小。
既然已经做了测试,我们不妨再做一个更加符合真实情况的测试。判断函数指针是否为NULL

——实际应用中,不管是调用前直接判断,还是函数一开始就判断,其实说起来都是多了一句判断,所以这个测试是非常符合实际的。
  1.     while(1)
  2.     {
  3.         Times = 0;
  4.         for(i = 0;i < 5000;i++)
  5.         {
  6.             //TestFun();
  7.           if(pTest != NULL)  
  8.              (*pTest)();
  9.         }
  10.         i = 0;  // for breakpoint here.
  11.     }
复制代码
现在测试的结果是 23.
这个结果就比较危险了。
23-17/17  将近1/3.
此帖出自编程基础论坛

最新回复

支持楼主!  详情 回复 发表于 2013-8-2 10:23
点赞 关注
 

回复
举报

7815

帖子

56

TA的资源

裸片初长成(中级)

沙发
 

我们接着测试变量的情形

既然已经开始了测试,我们就顺带把该测试的都测试完。
  1. static U16 gVar = 0;

  2. extern U16 Times;
  3. void main(void)
  4. {  
  5.     static U16 i;
  6.     static U16 pVal;
  7.     void (*pTest)(void) = NULL;
  8.    
  9.     Timer_Initial();
  10.    
  11.     pTest = TestFun;
  12.    
  13.     while(1)
  14.     {
  15.         Times = 0;
  16.         for(i = 0;i < 5000;i++)
  17.         {
  18.           gVar = 1;
  19.         }
  20.         i = 0;  // for breakpoint here.
  21.     }
复制代码
断点时,Timer = 13;

现在换成本地变量pVar
  1.     while(1)
  2.     {
  3.         Times = 0;
  4.         for(i = 0;i < 5000;i++)
  5.         {
  6.           pVar = 1;
  7.         }
  8.         i = 0;  // for breakpoint here.
  9.     }
复制代码
现在结果一样,还是13,我担心次数太少,于是我换成了10000次,结果都是27,可见, 对于变量,不管是 全局还是局部,开销是一样的。

误会误会,刚才那个pVar还是静态变量,我换成了 局部变量,去了static。结果是11,仍然略有差别,但是小了许多。

现在再试一下,通过指针的方式。
  1.     static U16 i;
  2.     U16 pVar;
  3.     U16 *pV = &pVar;
  4.    
  5.     Timer_Initial();
  6.    
  7.     while(1)
  8.     {
  9.         Times = 0;
  10.         for(i = 0;i < 5000;i++)
  11.         {
  12.           *pV = 1;
  13.         }
  14.         i = 0;  // for breakpoint here.
  15.     }
复制代码
现在是 16.
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

56

TA的资源

裸片初长成(中级)

板凳
 

最后测试 结构体封装的情形

因为在我们以后的实现方法里,结构体封装变量和函数指针是很常见的方法。
  1. void TestFun(void)
  2. {
  3. }

  4. static U16 gVar = 0;

  5. extern U16 Times;

  6. typedef struct
  7. {
  8.     U16 Times;
  9.     void (*pTest)(void);
  10. }pStr;

  11. void main(void)
  12. {  
  13.     static U16 i;
  14.     U16 pVar;
  15.     U16 *pV = &pVar;
  16.     void (*pTest)(void) = NULL;
  17.     pStr *Str = NULL,TestStr;
  18.    
  19.     Timer_Initial();
  20.    
  21.     while(1)
  22.     {
  23.         Times = 0;
  24.         for(i = 0;i < 5000;i++)
  25.         {
  26.             Str->Times = 0;
  27.         }
  28.         i = 0;  // for breakpoint here.
  29.     }
复制代码
这是通过结构体指针引用变量,结果是15.

现在换成 结构体指针引用函数指针
  1.     Str->pTest = TestFun;
  2.    
  3.     Timer_Initial();
  4.    
  5.     while(1)
  6.     {
  7.         Times = 0;
  8.         for(i = 0;i < 5000;i++)
  9.         {
  10.             (*(Str->pTest))();
  11.         }
  12.         i = 0;  // for breakpoint here.
  13.     }
复制代码
这时的结果是22;

最后再考虑一下,如果是结构体变量会如何。
  1.     while(1)
  2.     {
  3.         Times = 0;
  4.         for(i = 0;i < 5000;i++)
  5.         {
  6.             TestStr.Times = 0;
  7.         }
  8.         i = 0;  // for breakpoint here.
  9.     }
复制代码
现在是13;

最后是结构体变量引用函数指针
  1.     while(1)
  2.     {
  3.         Times = 0;
  4.         for(i = 0;i < 5000;i++)
  5.         {
  6.             (*(TestStr.pTest))();
  7.         }
  8.         i = 0;  // for breakpoint here.
  9.     }
复制代码
结果是22.

好了,至此,全部测试完成。
我们要整理一下结果
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

56

TA的资源

裸片初长成(中级)

4
 

结果汇总



我们来比较一下

对于变量而言。

直接调用 和 全局变量 相差不大,14:13 我重新测试了一次,仍是这个结果,让我略有点吃惊。全局居然比局部还快。

而通过结构体引用时,这个开销又稍微大了点,很明显的结果就是 通过指针 要 比直接变量 多。这个区别 是 14:16

除去全局哪一个地方比较奇怪,我们可以得出基本结论:
直接调用自然是最快的,而通过结构体变量取和直接取是没区别的,都是14.
这一点也可以在另一组对比作为验证。通过指针比直接调用,要多了2个单位。
而通过结构体指针比起通过结构体变量,也是多了2,再次验证一个事实

通过指针要比直接调用多2个时间单位,但是通过结构体和直接调用本身是没有区别的。

我曾经担心过,是不是5000次太少,于是我把次数拉到50000次,结果对于 结构体指针和结构体变量的结果
是 161:145
也就是说,这个区别不会超出10%的区别。14/!6 和 145/161 的区别可以认为没区别(0.875和0.901)

接着是函数的情形
前面关于 通过指针 和 直接调用 的 差别是类似的,都是2个相对单位。
唯一有点差别的是,通过结构体的时候,我们发现,通过结构体变量要比结构体指针快一些
当然我们要注意,也许是因为这个次数稍微有点少,假如我们把次数增多,也许最后会发现,这两者都有少许差别,
但可以认为这个差别不会超出现有比例的10%

我们可以实际试试 50000次的结果 直接调用 通过指针 通过结构体变量引用 通过结构体指针引用
161 185 185 234

这个比例仍然一致。

结论一下就是
关于通过指针和直接调用的差别是一致的。
但是,通过结构体引用变量的开销比起引用函数指针要稍微小一点,大致是2倍,或者只是纯粹碰巧,本来就是多了2个单位,因此刚好是2倍而已。

但不论如何,通过结构体指针引用函数指针的开销相对很大。

[ 本帖最后由 辛昕 于 2013-8-1 22:43 编辑 ]
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

56

TA的资源

裸片初长成(中级)

5
 
今晚的时间就这样过去了。
非常遗憾,不过这个测试确实让我们量化了我们的忧虑,也算是有意义。

可以看出对于我们最关心的区别

通过函数指针调用和直接调用

区别虽然看着很吓人,但实际上也只是 1/3

这个区别有时候仅仅意味着 1到2个时钟周期。

要注意一个细节:我测试的函数是空函数。
而实际上我们写的函数再小再小也有一个寄存器操作。

而更多的情形就更多语句了,这意味着,这个1/3的区别指的是 和 进出函数的时间开销相比,所以,在大多数情形下,这个比例很可能变成 1/30甚至是1/300

因此。
我们可以认为,尽管它确实多了一些开销。
但是这个开销的区别微不足道。
  1. static U16 gVar = 0;

  2. extern U16 Times;

  3. void TestFun(void)
  4. {
  5.      gVar = 1;
  6.      gVar = 2;
  7.      gVar = 3;
  8.      gVar = 4;     
  9. }



  10. typedef struct
  11. {
  12.     U16 Times;
  13.     void (*pTest)(void);
  14. }pStr;

  15. void main(void)
  16. {  
  17.     //static U8 Status = IDLE;
  18.    
  19.     //UltraSonic_Test();
  20.    

  21.     static U16 i;
  22.     U16 pVar;

  23.     TestStr.pTest = TestFun;
  24.     Str->pTest = TestFun;
  25.     pTest = TestFun;
  26.    
  27.     Timer_Initial();
  28.    
  29.     while(1)
  30.     {
  31.         Times = 0;
  32.         for(i = 0;i < 50000;i++)
  33.         {
  34.             //TestFun();
  35.             if(pTest != NULL)
  36.               (*pTest)();
  37.         }
  38.         i = 0;  // for breakpoint here.
  39.     }
复制代码
为此我故意在函数里做了一些无用功。
然后对比 直接调用  通过指针调用  指针调用前加非NULL判断
50000次的结果是
                      298        336                    363
我们可以看看,这个比例已经下降了。而这个函数十分简单,就算比起只有一两个寄存器操作,也不会相差太多。
                     而差别大致只是 10:11:12
此帖出自编程基础论坛
 
 
 

回复

954

帖子

0

TA的资源

纯净的硅(初级)

6
 
支持,跟紧楼主的帖子
此帖出自编程基础论坛
 
 
 

回复

2万

帖子

71

TA的资源

管理员

7
 
辛昕 加油
此帖出自编程基础论坛
加EE小助手好友,
入技术交流群
EE服务号
精彩活动e手掌握
EE订阅号
热门资讯e网打尽
聚焦汽车电子软硬件开发
认真关注技术本身
 
个人签名

加油!在电子行业默默贡献自己的力量!:)

 
 

回复

9176

帖子

5

TA的资源

管理员

8
 
支持楼主!
此帖出自编程基础论坛
加EE小助手好友,
入技术交流群
EE服务号
精彩活动e手掌握
EE订阅号
热门资讯e网打尽
聚焦汽车电子软硬件开发
认真关注技术本身
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/8 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表