随着应用程序功能的完善,时间和空间性能的优化已经成为我们目前工作的重心了。坦白的说,我并不擅长软件优化,甚至可以说我从心里不愿去做优化的工作,因为优化往往伴随破坏软件架构的副作用。但是到了目前这个阶段,整个系统在性能上的表现仍然不尽人意,已经不能再回避了。这里总结一下我所想到的基于GTK+应用程序的优化技巧,算是抛砖引玉吧,欢迎各位高手指教。
1.使用DirectFB作为GTK+的后端
DirectFB支持多进程模式,它用一个称为fusion的内核模块作为通信中介,用Master/Slave模型取代像TinyX所用的C/S模型,进行窗口操作时,不需要进程间切换,这在一定程度上改善了应用程序的响应速度。
2.使用DirectFB的pixbuf优化
DirectFB提供了一个pixbuf的主题引擎的优化实现,其实效果不错,我们在使用pixbuf的主题引擎时,感觉性能几乎没有下降。
3.延迟加载
强大的个人信息管理(PIM)是我们平台的特点之一,像名片,彩信,邮件,短信和日程等数据,我们没有加入硬性的限制,只受限于flash空间的大小。那么在显示列表时,一次性加载太多数据,必定会导致响应速度的急剧下降。为此我们对数据库和treeview做了一些改进,使用延迟加载的方式,只加载当前显示的和即将要显示数据,这不但节省了内存空间,也提高了响应速度。重要的是,加载速度随数据库容量的增加,只有微小的变化。
4.使用新的model代替model.cleartreeview和combobox等控件都使用了model,而model的特性是变化会通知view更新界面。这本来是它的优点,但有时在性能上却成为致命的因素,在重新刷新数据时,如果调用model的clear函数,该函数每删除一条数据都会触发一次signal让view更新,这使得重新加载数据变得非常非常慢。后来我们改为创建一个新的model,加载数据后再设置到view中,这样界面就只用更新一次。
同理,在第一次加载数据时,也要先把model的数据初始化好后,再设置到view里,而不是一条一条的插入进去,那样每插入一条都要更新一次界面,也会造成严重的性能影响。
5.Block signal
与前面一条类似,有的操作会触发signal,比如插入数据,会引发其它对象的更新。通常只是做一次操作,这没有什么问题,但在批量操作时,所造成的影响就变得显著了。这时可以先block住signal,等批量操作完成后,一次性触发signal。
6.在idle中加载数据
通常的做法是在应用程序初始化时,加载所有相关数据,这种方法让程序设计变得简单,但常常让应用程序起动变得缓慢。起动变得慢了,可能会让用户误以为没有点中起动图标,而多次重复去点击起动图标,造成重复起动应用程序,虽然我们可以保证应用程序单实例运行,但仍然会有一些系统开销,更重要的是起动缓慢会让用户不满意。这时可以先显示界面,让数据加载在idle里完成,等用户真正去操作界面时,数据也加载得差不多了。
7.使用动画标识当前的状态
这个方法并不能提高程序的速度,相反还有一些系统开销,不过它可以让用户知道当前状态,而不至于在界面没有反应时抓狂。这可以在应用程序起动过程中,显示一个动画标识正在起动,我们支持startup-notification功能,所以可以在桌面里统一处理,而应用程序自身不需要关心。
8.Prelink
prelink的功能正如其名字所示,事先link好所有共享库,避免在加载时才link带来的开销。它不仅可以加快起动速度,还可以减少内存开销。不过它虽然有点效果,但不要对它指望太高。
9.去掉DirectFB中不必要的驱动程序。
以前发现DirectFB中有很多共享库,它们是用于显卡加速的,里面有空间不小的data段,由于这些data段是私有的,而且它们在很多进程中加载,所以占去不少空间。我们并不需要这些加速驱动程序,可以直接删除它们。
10.只读数据用const修饰
我们知道const数据是放在rodata段中的,它在所有进程之间共享,即只加载一份拷贝。而普通数据是放在data段里的,它是进程私有的,每个进程都要加载一份拷贝。所以对于只读的数据,一定要加const修饰符,让数据放在rodata段中,可以节省不少内存空间。
11.尽量减少进程数
进程数的增加对于内存有很大影响,这不仅是因为内核创建进程时所占的空间,更重要的是,一般的进程都会链接很多共享库,每个共享库的中data/bss都要分配私有空间,最终占去很大的RAM空间。所以减少进程数对RAM空间的节省有明显的效果,合并进程,同时减少了进程间的通信开销,这也有利于提高时间性能。我们把SCIM从7个进程合并为两个进程后,输入法的响应速度有明显提高,占用的RAM空间也减少了。我们现在着手进一步合并后台服务进程。
12.使用Thumb指令代码ARM指令
Thumb是16位的,把程序编译成thumb指令,可以大大降低代码段的大小,不但节省了flash空间也节省了RAM空间。同时由于数据总线是16位的,程序的运行速度较ARM指令也有很大提高。我们目前正在研究,从可行性上讲,应该没有什么问题。
13.减少不必要的依赖。
前面说了共享库的data/bss段是私有的,每个进程都有一份拷贝。而gcc并没有在链接时做优化处理,不管你是否真正调用了某了个库的函数,只要在命令行参数指定该库,它就会链接进来。所以在创建Makefile时,一定要尽量避免链接不必要的共享库。
附带说明一下,linux的内核虽然实现了父子进程之间的Copy on write技术,避免不必要的内存复制,但这对data/bss段并没有什么意义,原因是fork之后,在执行exec之类的函数时,整个进程空间已经被替换了。data/bss并不会在父子之间共享,相反,它们立即就被创建并初始化。
14.Minimo用gtk编写而不用XUL编写
我们用gtk重写minimo之后,minimo起动速度由原来的40秒,提高到了后来的7秒,看来XUL的性能表现确实不佳。后来又采用idle加载机制,现在从起动到显示界面仅要2到3秒钟的时间。
15.dlopen 代替exec
我记得ALP使用了这一招,据说效果不错。它把应用程序编译成共享库而不是可执行文件,在执行时先fork一个进程,再通过dlopen把应用程序加载进来运行。这样仍然是多进程的,但是避免了exec这样的费时的操作,同时可以享受父子进程之间的copy on write技术带来的性能提升。不过令人我惊异的是,我的实验结果得到却是相反的数据,有时间我再分析一下。
16.cache压缩
有一种称为cache压缩的技术,它可以让cache压缩后存放在内存中,把省出的空间供其它进程使用。我们还没有来得及测试,有兴趣的朋友可以参考http://linuxcompressed.sourceforge.net
17.Gtkrc cache
据说Nokia的N770/N800为加快应用程序的起动速度,使用了一种称为gtkrc cache的技术,具体细节不太清楚,大概可能是把gtkrc从原始的文本文件存为二进制文件,使得gtkrc的加载速度变快吧。
18.XIP
XIP可以让应用程序直接在norflash中执行,而不必加载的内存中执行。我们没有使用norflash,所以也就没有去研究,不过这种思想倒是挺不错的。
19.使用压缩的文件系统
像ramfs,jffs 2和yaffs 2等文件系统都可以压缩,压缩比率也相当可观,能够节省大量的flash空间。我们目前使用的jffs 2,据说yaffs 2表现更为优异,等测试之后,再决定最终使用哪一种吧。
20.挖掘硬件加速功能
现在的CPU一般都带有硬件加速功能,特别是多媒体相关的应用,比如硬件编解码和MMX指令等等,这对多媒体应用程序相当有效,要充分发挥,不用白不用,不要浪费了。
21.使用更小的函数库
现在不少函数库,都有多种实现,可以考虑使用针对嵌入式设备优化过的版本。比如uclibc代替glibc等等,虽然介于以前使用uclibc的经验,我决不赞成使用uclibc,但类似的方法还是可取的。
22.奉行简约主义
像词典的词库,输入法的词库和字体等数据,都要尽量精简到最少,不能像PC那样有多少就放多少。
在以上方法中,有的可以同时提高时间和空间性能,有的只能提高时间性能或者空间性能,还有的是时间性能和空间性能互换的,要根据具体情况进行取舍。
查看本文来源