[求助]再问tornado中代码的执行效率问题
我的见解,仅供参考
1. 7000~8000行代码编译出来30M,实为少见。我认为有两个可能的原因:
其一:在编译过程中添加了调试信息,也就是编译器的选项中使用了 -g(如果是gcc,或者dcc)选项,这时,目标文件中添加了调试信息,优化级别应该是0,这往往会造成目标文件大小数十倍的增加。如果去掉调试信息,启用优化(一般VxWorks的应用程序用gcc编译选项用-O2),目标文件应该会小很多。Tornado的“build”TAB中有此选项,而且这种原因的可能性较大些。
其二:程序代码的数据段占用非常大,也就是说程序中存在大量的初始化了的全局变量或者静态变量。如果全局变量和静态变量可以不初始化,可以尝试不对其进行初始化(不设置初始值),这样,他们将存在bss段中,目标代码中仅使用占位符进行定义。这个可能性不太大,影响也不十分明显(视你的程序而定)。
2. VxWorks中允许使用寄存器变量,通常为了方便和各平台兼容,使用FAST作为寄存器变量的前置修饰符,例如:FAST int myVar;寄存器变量使用是有一定限制的,也是有其目的的,从物理上来说,使用寄存器变量的好处就是避免与外部存储总线打交道,缩短该变量的访问时间。因为寄存器是有限的,所以通常寄存器变量不能过多使用,一般用于非常频繁访问的函数的非常频繁访问的局部变量。但这种寄存器变量在有缓存且启用了缓存的系统上,效果不是十分明显,对于程序效率的影响不会特别大。非常推荐你看看WindRiver自己写的那些源代码,里面可以找到很好的参考。
仅供参考,且盼反馈。
首先多谢你的回复,受益非浅。
但是我没有在tornado的build菜单中找到gcc或者dcc的选项,是不是tornado版本的问题?我的是tornado2.2,盗版的。是不是正版的才有这两个规则?我的到是有个gnu选项,不知道是不是编译规则?
另外我的程序代码中确实定义了几个比较大的缓冲区,大概总共是二十多兆的空间(原来是30几兆),也给它们赋了初值,编译出来的结果是这样的:vxWorks: 661568(t) + 21026224(d) + 33552(b) = 21721344
如果不赋初值出来的结果是这样的:vxWorks:661568(t) + 460464(d) + 20599376(b) = 21721408
不知道这里面的t、d、b分别代表的是什么?
寄存器变量的问题我原来用的是register,速度未见提升,回头我用FAST再测一下试试。
再次感谢!
to taocj:
关于gnu和diab,在Tornado2.2的发行版中,有些是没有带diab C 编译器的(比如x86、ARM等),有些是有diab编译器的(比如ppc,CF等)。用什么编译器,通常对我们进行编程的影响并不大,就Vxworks的编译器而言,diab可能确实是好些,主要表现在编译出来的目标文件大小大概为gnu的95%左右。毕竟是WindRiver自己的产品,而且是收费的。但用gnu编译器也是非常不错的,有没有diab编译器,与D版与否无关。
关于t、d、b。t是text的意思,亦即纯代码,也就是常说的文本段;d是data的意思,亦即数据,也就是常说的数据段;b是bss的意思,就是没有初始化的全局变量和静态变量,也就是常说的bss段。
对于你的情况:661568(t) + 21026224(d) + 33552(b) = 21721344
文本(代码):约为660k,不算大,但也不小了。
数据(初始化了的全局变量和静态变量):约为21M,超大。
bss(没初始化的全局变量和静态变量):约为33k,不大不小。
从上面可以看出,数据段超大是造成你目标文件大的主要原因。
有两种改进意见仅供参考:
第一: 仅定义全局变量,但不初始化它,或者如果必须要初始化的话,在代码中初始化它,也就是说,在程序中,如果有下面现象:
int myArray[1000000] = {1,2};
则将其改为:
int myArray[1000000];
int myFunction(...)
{
/* perform init */
myArray[1] = 1;
myArray[2] = 2;
...
}
第二:如果你的缓冲是那种“一分配则在本次上电运行过程中不再释放,不再换地方存储”(其实,你声明全局变量就是这样的。),还有一种比较好的方法可以实现,那就是将你的静态内存分配改为动态内存分配。简单的做法就是使用malloc()函数。例如:
void * myBuf_Ptr = NULL;
STATUS myFunction(...)
{
char * pBuf;
myBuf_Ptr = malloc(100000000);
if (myBuf_Ptr == NULL)
{
/* 内存分配失败,报错 */
logMsg("memory allocate Error.%s,%s,%d",(int)__FILE__,(int)__FUNCTION__,(int)__LINE__,0,0,0);
return ERROR;
}
/* 得到了一个动态的空间。可以使用它作为缓冲区了。 */
pBuf = myBuf_Ptr;
/* 必要的初始化 */
pBuf[0] = 1;
pBuf[1] = 2;
...
}
如果你的缓冲区是结构体的话,做法也类似。
需要注意的是,malloc()的使用需要十分小心,通常情况下,如果分配了“一直”不要释放的问题不大,但如果频繁分配又释放,在嵌入式操作系统中是不推荐的,特别是VxWorks 5.x。它没有“堆”的管理,分配释放做多了,容易有内存碎片,日久容易造成不稳定。
另外就是,使用动态分配,特别注意对动态内存空间指针的合法性进行判断,一定要避免使用空指针。
其实,C语言作为一种“微内核”(不需要啥基本库支持就能运行,其实如果不需要标准库支持,它只需要堆栈指针就可以了)的编程语言,灵活性是很强的,对于我们做嵌入式来说,研究它的行为是十分有趣的,比如说:C语言的每一句话究竟会产生怎样的汇编代码和CPU动作呢?参数传递是如何进行的?等等。
关于寄存器变量,我觉得:使用与否可按前面我提到的“频繁”与否的规则来进行,但要看出它的效果(特别是宏观上变快了与否),是不那么明显的,通常在驱动程序、中断服务程序和频繁调用的系统基本函数比如系统IO函数中,才会使用到寄存器变量,而在应用程序中,用到的并不多。
还有,那个FAST通常就是register。WindRiver为了代码一致性好,就定义这个宏。
再盼反馈。
再咯嗦几句:
代码效率其实主要依赖于程序的实现,算法等。还有一些辅助手段:
寄存器变量:对于“整类型”的局部变量,使用寄存器变量可以减少通过RAM总线访问(普通局部变量)的时间);
内联函数:对于调用频率非常高的小尺寸代码,使用内联函数(inline),通过牺牲代码尺寸来减少堆栈操作。
另外,你可以写个 test case,如下:
case1:
int myArray[100000000] = {1};
void myFunc(void)
{
myArray[0] = 1;
return;
}
case 2:
int myArray[100000000];
void myFunc(void)
{
myArray[0] = 1;
return;
}
分别编译,比较一下目标文件尺寸,然后,对于case2,启用和关闭调试信息,再比较目标文件尺寸。 最后,比较一下二者所生成的汇编代码分别是怎么样的。就可以很清楚的发现初始化与否的区别,data和bss的区别,占位符的含义。
1.我想先不使用分配动态内存的方法。
将我的全局变量不初始化后编译的情况是:vxWorks:661568(t) + 460464(d) + 20599376(b) = 21721408
也就是说b大了,d小了,但是总体的大小还没有变,怎样将总体的大小变小呢?
2.关于寄存器变量,我的算法中要频繁对一个变量进行赋值操作,对变量的赋值应当是对外部存储器操作吧。因此考虑用它来提高代码的效率。但是使用之后效果确实不是很明显,有的资料上说嵌入式系统中可能会把寄存器占满,从而用户无法用到寄存器变量,不知道有没有这方面的原因?
3.内联函数的问题,我也考虑过,但是在函数前加上inline后编辑后会报错(syntax error),不知道是什么地方设置的不对,还是tornado不支持?
4.你最后提到的看二者之间所编译的汇编代码的区别不知道从何处看?另外启用和关闭调试信息在什么地方设置呢?
感谢在不言中……
1. vxWorks映象,为了避免内存检测错误等目的(具体不详),是把bss象data那样处理了的,它的目的是将bss段清零。这是它的编译规则,我们通常最好不要修改它(如果要修改也是可以的,可以改target/h/tools中的链接脚本和target/h/make中的编译规则)。如果要改变总体大小,有两种方法:
其一:把应用程序和VxWorks系统编译成一个文件,使用ROM类映象,vxWorks_romCompress(,hex,bin)或者不压缩的(当然还是压缩好,需要的ROM空间小呀)。这类映象是把bss清零的过程省略掉了的,bss的大小影响就消除了。
其二:把VxWorks系统映象和应用程序编译成不同的文件。待VxWorks启动成功后,加载应用程序文件。条件是你的系统必须要有某种文件系统支持,比如Flash的tffs或者CF的dosFS卡等。这样,系统映象与应用程序独立,应用程序编译成一个.out文件,尺寸是非常小的(前面你做的测试已可看出)。
当然,如果这两种你都不愿意采用,那可能malloc()又成了最好的选择。
2. 有这种可能,这不仅与系统的寄存器使用有关,还与你的编译规则有关。对于你的情况,VxWorks映象属于操作系统,其中的大部分代码与硬件相关,都是不能优化过头的了,可以观察下,sysLib.c等文件使用了-fvolatile来强制其访问的存储器是不可缓存的,优化级别也很低,不同优化级别的代码执行起来的效果是有所差别的(生成的汇编也截然不同),所以,还是建议你单独编译应用程序,使用较高的优化级别(-O2通常)。关于寄存器的使用情况你可以在target/h/arch/<yourCPU>/asmxxx.h中找到,文档可以在编译器参考手册中找到。实际上,前面已提到,如果你的CPU有Cache的话,使用寄存器变量带来的好处是微乎其微的,因为cache的速度也非常快。再说,就算没有cache,使用resigter与否,宏观效果是很难区分的。
3. 内联函数可用。可以在Vxworks自己的那些源码和头文件系统找到范本。搜索一下就可以得知了。
4. 看汇编,借用一些反汇编工具。个人推荐IDA Pro,支持的CPU架构比较多,使用方便,网上又好找。说实话,一个反汇编工具可以是搞嵌入式的较为必须的工具呀。
5. 启用和关闭调试信息:tornado的工程中的build栏里面是有的,在C/C++ complier那个里面。加调试信息是 -O -g,不加是 -O2 (-g就没有了)。。其实,推荐自己写makefile在命令中编译,方便呀,tornado的代码编辑能力又实在不敢恭维。
不必言谢。共同学习。你的钻研精神好强呀。。必有作为!
回复
有奖活动 | |
---|---|
【有奖活动】分享技术经验,兑换京东卡 | |
话不多说,快进群! | |
请大声喊出:我要开发板! | |
【有奖活动】EEPW网站征稿正在进行时,欢迎踊跃投稿啦 | |
奖!发布技术笔记,技术评测贴换取您心仪的礼品 | |
打赏了!打赏了!打赏了! |
打赏帖 | |
---|---|
【换取逻辑分析仪】自制底板并驱动ArduinoNanoRP2040ConnectLCD扩展板被打赏47分 | |
【分享评测,赢取加热台】RISC-V GCC 内嵌汇编使用被打赏38分 | |
【换取逻辑分析仪】-基于ADI单片机MAX78000的简易MP3音乐播放器被打赏48分 | |
我想要一部加热台+树莓派PICO驱动AHT10被打赏38分 | |
【换取逻辑分析仪】-硬件SPI驱动OLED屏幕被打赏36分 | |
换逻辑分析仪+上下拉与多路选择器被打赏29分 | |
Let'sdo第3期任务合集被打赏50分 | |
换逻辑分析仪+Verilog三态门被打赏27分 | |
换逻辑分析仪+Verilog多输出门被打赏24分 | |
【分享评测,赢取加热台】使用8051单片机驱动WS2812被打赏40分 |