Symbian OS 精要
性能优化诀窍
确保您的软件通向成功之路畅通无阻 “Symbian 签名”(Symbian Signed)是一个得到行业 支持的方案,它使 Symbian OS 软件开发者能够为 自己的应用软件获取一个数字认证签名。若想获得 Symbian 签名,应用软件必须通过各类标准化测试。 某些网络经营商和手机生产商(例如 Orange、 Nokia、Sony Ericsson)现在要求对于通过其渠道发 布的用 C++ 和 AppForge MobileVB/Crossfire 开发应 用软件都必须获得 Symbian 签名。 获得了 Symbian 签名的应用软件在使用时不会出现 “应用软件来源不可信”(application source untrusted) 的警告,并可在企业对企业 (B2B) 的应用软件目录 上发布。获得认证签名的独立软件开发商 (ISV) 还可 使用如下标志:
欲知详情,请访问网站:www.symbiansigned.com
Symbian OS 精要
性能优化诀窍
from
性能优化诀窍 Symbian OS 精要系列丛书 1st edition, 12/06 出版人: Symbian 软件有限公司 地址:2-6 Boundary Row Southwark London SE1 8HP UK(英国) www.symbian.com 商标、版权和免责条款 “Symbian”、“Symbian OS”和其它与 Symbian 相关的标识都是 Symbian 软件有限公司的商标。Symbian 软件有限公司承认本书中所提及的所有第 三方的商标权。Symbian 软件有限公司 2006 版权©。版权为本公司所有。 本 书 中 任 何 内 容 未 经 Sy m b i a n 软 件 有 限 公 司 的 书 面 许 可 不 得 复 制 。 Symbian 软件有限公司对本书所含内容的恰当性与准确性不作任何保证或 担保。本书的内容除了供一般参考之用,不应该被用于任何其它目的。
编稿: Andrew Langstaff
设计师: James Mentz
执行编辑: Freddie Gjertsen Satu McNabb
审稿: Mel Pullen Alon Or-Bach Krzysztof Bliszczak
设计顾问: Annabel Cooke
中文版审稿: 焦霖 段凯
前言 随着当今日益增加的带宽容量和各种眩目的功能的出现, 我们发现对智能手机软件的处理效率和能力的要求也不 断提高。为了满足此类需求,所编写的软件必须力求高 效。尽管问题还没有严重到必须考虑每个“运算周期” 的效率,但是有一些简单的原则我们应该留意。
目录 “性能”是什么................................................................. ..1 性能的重要性.....................................................................1 性能大敌 ............................................................................1 代码过多,数据不足 ......................................................... 2 在循环中重复代码 .............................................................3 堆存储的低效用法 ............................................................. 6 对函数库缺乏理解 ............................................................. 7 类型强制 ............................................................................ 9 低效的文件使用 ............................................................... 11 设计模式的滥用............................................................... 14 通用及“永不过时”代码 ................................................15 在模拟器上开发、测试....................................................17 程序员、编译器、代码....................................................18 短诀 ..................................................................................19 一个有用的工具:取样分析器.........................................20
1
“性能”是什么 性能是一个装置能够显示的一些可度量的特性,如开机 时间、ROM 大小、RAM 的使用、图像查看和电池寿命。 手机的用途及所提供的特性经常会对这些性能指标提出 具体的要求。为了满足这些要求,手机软件必须有针对 性的进行设计与实现。
性能的重要性 通常满足性能要求的做法是提高 CPU 速度、或分配出大量 RAM 来用于缓存。但这两种手段对手机制造商并不真正 适用,因为手机的设计必须考虑电池消耗及硬件成本的 限制。
性能大敌 智能手机所表露的大多数性能问题,最终都可归结为少 数几类。接下来将对这些问题进行探讨。
2
代码过多,数据不足 通常,在开发过程中,应用程序会有一些影响其行为的参 数。这些参数经常被存放于一个文件,在程序启动时处理。 当这些可调节的配置变为静态时——或者是由于需求得以确定, 或者是由于合适的缺省值最终被选定——问题就来了。此时 可以将参数硬编码。但这经常意味着对应用程序进行再加工: void SomeCode(void) { // Open file // New ReadStream // Read some data members into a struct // Close file, etc. }
同样的问题也会发生在堆 (heap) 上动态创建的类似数据, 如果该数据本可以在编译阶段生成: void ConstructL(void) { TFuncTable *fns = new (ELeave) TFuncTable; fns->iFunc1 = ExampleFunc1; fns->iFunc2 = ExampleFunc2; fns->iFunc3 = ExampleFunc3; iFuncTable = fns; }
3
在此例中,最好采用已初始化的 TFuncTable 的一个 const 拷贝,然后让 iFuncTable 指向该拷贝,或者, 最好是使用此 const 版本来替代 iFuncTable。 此类问题往往十分隐蔽,尤其是当 function table 以某种 接口类的形式出现时。 在另一些例子中,问题出在很难用易懂的 C++ 语言来表 达数据:一个从文件读入的数据结构可能包含一些子结 构。这类结构经常不易被表达为易读的 const struct 或 const class。 开发人员甚至会选择使用“生成代码”的解决方案,读 入一个常人可理解的数据文件、再将其转换成为 C++ 代 码并进行编译。
在循环中重复代码 冗余的计算经常与复杂类型的构造相伴。 请看下面的例子: ExampleClass::SimpleOperation(SimpleType a, SimpleType b) { //creation of a complex type – this is
4
//unnecessary, see text ComplexType c = b.MakeComplex(); // some other code } CExampleClass::DoSomeComplexComputation(...) { SimpleType a,b; while(moreToDo) { //some code SimpleOperation(a, b); // some other code that does not change //variable b } }
在上述代码中,函数 SimpleOperation()在一个循环 中被调用。在各次迭代里,复杂类型 (ComplexType) 反复 通过一个 SimpleType 创建,但是在后面的代码里未被 修改。此重复产生的 ComplexType 是不必要的,将造 成浪费资源,并可能严重影响性能。显然,倘若能够将 一个 ComplexType 传入 SimpleOperation(),可避 免其重复构造:
5
ExampleClass::SimpleOperation(SimpleType a, ComplexType &b) { // some code } ExampleClass::DoSomeComplexComputation(...) { SimpleType a; ComplexType b; //create b as a complex type //from outset while(moreToDo) { //pass b as a ComplexType instead of a //SimpleType SimpleOperation(a, b); //some code } }
总之,应小心确保重复的计算或处理不要发生在频繁使 用的循环中。
6
堆存储的低效用法 在嵌入式系统里,经常需要采用堆 (heap) 空间而不是栈 (stack) 空间。如果不慎可能造成过量的堆空间的调用。 void LoopWithHeap(void) { while(moreToDo) { CData *temp = new CData; GetData(temp); ProcessData(temp); delete temp; } }
应尽可能重复使用临时变量: void LoopWithHeap(void) { CData *temp = new CData; while(moreToDo) { GetData(temp); ProcessData(temp);
7
temp->Reset(); } delete temp; }
另外一个堆空间使用不当的例子,在于使用分段数据结 构 (segmented data structure) 而其分段粒度对于处理的 数据量而言过细。 更多使用堆空间不当的原因,来自于过度依赖 realloc。 设计不当,导致堆存储单元需要扩充,通常引起 alloc、 free、memcpy 调用。
对函数库缺乏理解 API 文档很少包括关于实现的深入解释。用理解错误或者 不甚理解的 API 进行编码,可能导致重复或不必要的数据 处理和转换等问题。 请看这样一个类,该类提供对一个数组的存取并用函数 SetElement 实现数组的边界检查: void ArrayClass::SetSize(int aSize) { iMaxLength = aSize; }
8
void ArrayClass::SetElement(int aPos, unsigned char aChar) { if(aPos >= 0 && aPos < iMaxLength) { iRawArray[aPos] = aChar; } }
再看一个采用此类的程序,该程序需要向数组添加一些 元素: void ExampleClass::FillArray() { //some code myArray.SetSize(bytesToProcess); for(currentPos = 0; currentPos < bytesToProcess; currentPos++) { myArray.SetElement(currentPos, aByte); } }
9 这里的效率低下问题出在 SetElement 所从事的边界检 查是不必要的,因为调用的函数处于一个循环中,该循 环已经设定了数组的上限。 问题的成因也许是多方面的。可能 ExampleClass 的 开发人员没有意识到 ArrayClass 会做边界检查,或 者 不 知 道 A r r a y C l a s s 有 一 个 更 适 合 的 A PI , 或 者 ArrayClass 的实现者没有提供此 API,并没有预料到该 类会被如此使用。
类型强制 当数据设计不佳时,会浪费处理时间于数据类型转换。 通常仅仅是静态转换,而且是在准备向外部 API 传数据之 前发生。 请看如下代码,一个“追踪数据类型”的例子:
TInt intDrive; TChar ch = ((*drives)[i])[0]; RFs::CharToDrive(ch,intDrive); TDriveUnit curDrive(intDrive);
10 代码需要使用的是 TDriveUnit 类型,但存储的是包含 盘符名称的字符串,结果需要对其进行三步处理才能使 其变为可用的格式。倘若此操作被用于频繁使用的循环 中,则可能消耗大量的处理时间。最好与盘符名称字符 串一起存贮 TDriveUnit,或者干脆仅存储 TDriveUnit 数据类型,以便直接使用。 在另一些例子里,有些不必要的临时数据对象被构造在 栈上,仅仅是为了改变访问一个数据对象的接口。 请看下例,在这里多余的对象创建只是为了调用函数:
iDllEntry.iName.Des().Zero(); iDllEntry.iName.Des().Append(aPath.Drive()); iDllEntry.iName.Des().Append(KSysBin); iDllEntry.iName.Des().Append(*resourceName); iDllEntry.iName.Des().Append(KDllExtension);
这个例子显示了两个问题: 主要问题是调用 Des()函数带来的类型强制转换构造了 一个临时对象,在此例中,发生了五次,而其结果原本 可以在本地存贮、重用:
11
TPtr des = iDllEntry.iName.Des(); des.Zero(); des.Append(aPath.Drive()); des.Append(KSysBin); des.Append(*resourceName); des.Append(KDllExtension);
另一个问题不是很明显,涉及编译器的使用。Des()函 数的设计者将其声明为内联 (inline) 函数,但当编译器被 要求优化代码量时,因为该函数被频繁使用,编译器会 决定不使用 inline 修饰符。
低效的文件使用 这一范畴包括一系列问题,有些不仅涉及文件的误用, 还更广泛地涉到及其它不同于 RAM 一样可以即时存储的 数据源,例如,来自某些硬件及网络的数据。 文件系统的低效使用,可能源自于把文件当成数据库来 使用,用目录结构与文件名称格式来定义数据库的数据 结构。
12
另一个问题可能来自“同步”设计,以顺序操作的方式 从文件或其他数据来源成块 (block) 的读取并处理数据。 这可能意味着数据处理的操作在整个数据块被读取的期 间处于等待状态。 还有一个常见问题如下例所示,文件被频繁、小量读取: EXPORT_C CColorList* ColorUtils::CreateSystemColorListLC(RFs& aFs) { … CDirectFileStore* store; store=CDirectFileStore::OpenL(aFs, KGulColorSchemeFileName,EFileRead| EFileShareReadersOnly)); … RStoreReadStream stream; stream.OpenL(*store,store->Root())); … CColorList* colorList=CColorList::NewLC(); stream>>*colorList; return colorList; }
13
问题隐藏在重载 c++ >> 操作符的实现,下面是 InternalizeL 函数的一部分: aStream>>card; const TInt count(card); TRgb rgb; for (TInt ii=0;ii
>rgb; iEikColors->AppendL(rgb); }
我们可以看到,该函数为每一个内嵌的类进一步调用重 载的 >> 操作函数。跟踪这些函数调用,显示函数在内存 中建立的结构是 32 位元的数据块,每个数据块导致一次 新的文件读取。即使文件服务器对该文件具有提前读取 的缓存,函数调用的深度仍将带来性能问题。 考虑如下方案: aStream>>card; const TInt count(card); aStream->ReadL(iEikColors, count * sizeof(TRgb));
速度快得多,但代价是需要小心确保 TRgb 的内部格式没 有变化。
14
设计模式的滥用 设计模式是把各种软件问题归纳到一些已知的类中。从 而常见的、经过测试和考验的解决方案能够更容易的被 找到和实现。然而,设计模式绝不能取代对具体问题的 切实思考,并获得解决方案。 即便选用了某个特定的设计模式,也可能导致进一步的 问题。设计模式通常意味着采用全面的面向对象的实现, 而这些面向对象的抽象方法并非永远恰当。盲目照本宣 科式的对其进行实现,往往导致性能开销,或者增加代 码的复杂性。 请看下面的代码:设计采用的是状态模式 (State Pattern)。 对状态机的初始化,实现者决定采用工厂模式 (Factory Pattern): CL2CAPStateFactory* CL2CAPStateFactory::NewL() { CL2CAPStateFactory* factory= new (ELeave) CL2CAPStateFactory(); CleanupStack::PushL(factory); // Create all the new states factory->iStates[EError]
= new (ELeave)
TL2CAPStateError(*factory);
15
factory->iStates[EClosed] = new (ELeave) TL2CAPStateClosed(*factory); factory->iStates[EListening] = new (ELeave) TL2CAPStateListening(*factory); // etc... CleanupStack::Pop(); return factory; }
然而,仔细审阅该类的用法及所含的状态类,我们看到 每个状态所持有的“工厂”的引用仅被用于状态转换; 这意味着,倘若该状态模式以不同的方式实现,复杂的 创建操作及其所有工厂模式的代码都是不必要的。
通用及“永不过时”代码 此范畴的问题来自对框架 (framework) 和插件 (plugin) 的 过度依赖。还包括为了软件的易开发性而牺牲性能的做 法。 考虑一个应用程序,该应用程序把一些配置参数存于一 个“.ini”文件内。在开发时,这些参数可能经常被修改, 故常常用一个易于编辑的格式来存放。然而,一旦这种 存放格式被固定,那时装载、解析此文件可能带来性能 问题。
16
框架与插件,是 Symbian OS 设计的基石之一,但却经常 被不恰当使用。 使用框架的代价是代码量增加,因为需要管理代码来检 索插件,检查插件有无改变,装载及链接插件的 DLL,再 加上用于选择某个特定插件的额外代码等等。此外还会 带来性能开销,因为框架通常对外提供一个通用的客户 接口,而从内部将请求传至所用的插件。 当需要考虑到动态、及时的灵活性时,这些代价都是可 以接受的。然而,当某一部分插件的使用在该段软件的 生命周期中被确定下来时,这些成本的消耗将不能容忍。 防止过时的代码,可能是值得的,但同时,当此技巧被 滥用时,在代码量、性能方面都将付出代价。
17
在模拟器上开发、测试 Symbian OS 模拟器 (emulator) 是作为开发工具提供的, 因为它能缩短从代码编写到软件运行的周期。亦使开发 者能够在运行时进行调试,避免了使用昂贵的硬件工具。 模拟器带来的这些便利是无价的,然而,这也导致很容 易忽略实际硬件测试,特别是在受到发布期限临近的压 力时。硬件测试可能只被用于在发布前的功能测试甚至 只是保证代码能够在硬件上运行。这样的开发方式,可 能导致开发者对代码在实际硬件设备上的表现并不了解, 即使这未必直接带来性能问题,也可能意味着在开发的 最后阶段或产品推出后才发现问题。 这样给客户的印象是:测试不够充分、质量控制不足。 另外,改进余地受到了限制,因为那时已经时间有限, 对大的架构上的改动难以达成共识。
18
程序员、编译器、代码 除了需要理解系统、编程语言之外,掌握所使用编译器 的基础知识,也是十分重要的。许多现代编译器含有一 系列的优化步骤,这些优化步骤将根据所需的指标产生 代码,包括较小的代码量、或较快的执行速度。因此需 要弄清楚编译器所选择的指标,理解其对编译器所生成 代码的影响。 同样重要的是,不要假定某个编译器所使用的“技巧”也 会被其他编译器所使用。
不要与编译器对着干 了解你的编译器,明白其产生的代码与你所写代码的关 系。不要写过于指令性的代码。这样可能强迫编译器以 某种可能影响效率的方式产生代码。
学一点汇编 汇编程序经常被认为是“魔法”,一般软件开发人员敬而 远之。然而,对汇编的基本了解对于全面理解代码如何 在某个平台上运行极其有用。
19
短诀 除了本书提到的那些“性能大敌”之外,还有一些小地 方在写码时需要留意。许多都是常识,通常由“编码标 准”规范化,但某些值得在此一提。
在循环中存储调用结果 在循环的条件语句中避免函数调用。尽可能使用局部变 量来存储函数调用返回的值,除非该结果在每次迭代时 都改变。
尽量使用引用或指针 以引用 (reference) 传送参数通常是好的作法,但不要使 用引用来传递整数类型假如其仅是用于读取。
不要展开循环 有了现代编译器,这类优化不再是必要的,而且甚至可 能起相反作用。编译器会做这种优化。
避免长条件语句 (if … then) 链 最好使用 switch 语句,因为其实现的性能较好。如果条 件不是常整数,例如是字符串,则考虑在用 switch 前使 用查找表 (lookup table)。
20
适当使用 const 修饰符 将只读的变量标出,编译器会产生更高效率的代码。
一个有用的工具:取样分析器 这一节针对拥有 Symbian 许可证权限和某些特殊级别 SDK 权限,或使用参考硬件集成板 (reference board) 的开 发人员。 取样分析器 (Sampling Profiler) 能够提供原始性能数据并 用统计的方式展示设备上的软件运行。其采取的方法是 在每毫秒的时段记录线程的 ID 及当前的程序计数器 (program counter) 值。其还带有一个命令行程序,可用于 在 PC 上分析数据。 得到的信息则可用于调查性能问题,从事代码检查以确 定可能的瓶颈。
创建 ROM 使用分析器 (profiler),先需要将其添加到 ROM 之内,把 profiler.iby 添加到 buildrom 命令行中即可: buildrom h4hrp techview profiler.iby
启动分析器 控制分析器的最简单的方法,是通过 eshell。如命令行: start profiler start
21
该命令行启动另一个线程,分析器运行在此线程内。你 可以用 <Shift> 结合键切换回到其他任务。
运行欲分析的代码 在此时分析器将一直运行并进行取样。在启动欲分析的 代码之前稍做停顿,将帮助你在视觉上区分出不同的分 析阶段。
停止分析器 分析过之后,切换回 eshell 停止分析器: profiler stop
然后关闭分析器数据文件: profiler unload
读取分析数据 在参考硬件集成板上的 C 盘根目录下应该有一个名叫 profiler.dat 的文件。可将其拷贝到 MMC 卡上然后放到 PC 上以进行分析。
根据活动分析数据 应该将数据转换成适于 Excel 显示的形式,以产生图形, 获得所分析软件活动的总体图像。
22
拷贝分析文件 将其拷贝至 ROM 文件夹,因为你将需要从符号表中提取 名称。通过运行下列指令来创建活动格式文件: analyse –r h4hrp_001.techview.symbol profiler.dat -v –mx > profile.xls
建立活动图形 在 Excel 里打开 profile.xls 文件。为了确保图形显示线程 的名称,删除数据的前六行。这是汇总数据,如包含在 内则会导致图形混乱。类似地,第一列的时间标记也会 导致图形混乱,但你不能删除它,因为需要用其来交叉 参考所感兴趣的区域与实际时间。 选择所有数据并单击‘图表向导’(chart wizard)。这将开 启一个四个步骤的导航。 • 在图表类型中选择‘面积图’并在子图表类型中选择 ‘堆积面积图’,然后选择‘下一步’ • 调整区域,省去第一列的时间标记。将 A 改为 B。 例如: =profile!$A$2:$V$941 改为 =profile!$B$2:$V$941, 然后选‘下一步’ • 忽略下一个视窗,选择‘下一步’,再选择‘作为新 工作表插入’,然后按‘完成’。
23
选择活动区段及线程 查看建立的图形,应该可以看出你的程序在做什么、在 何时做,让你能定位到您所需的区域。可用鼠标移至数 据区,弹出的窗口会告诉你哪一个线程在那一点在运行。 可以使用该点的行号在数据表的第一列中找到其时间标 记。另外,可以删除那些不感兴趣的行。请记住,Excel 会把行重新编号,故应从后面开始删除。图形会根据新 数据重新绘出。
根据函数建立列表 一旦知道时间标记的范围及其范围内所运行的线程,可 以根据活动次序建立一个函数清单。 例如,假如你对时间标记范围 51300 与 76900 之内 EFile 线程所调用的函数感兴趣,可使用下述指令: analyse –r h4hrp_001.techview.symbol profiler.dat -bf –p –s51300-76900 –t EFile* > analysis.txt
• 取样范围,在 –s 之后和两个数字之间的连字符 (-) 前 后不得有空格 • 取样范围是数据表的第一列所含的时间标记 • 线程名称,在 –t 之后确有空格,可在名称前后使用 通配符。
24
可将输出文件读到一个文本编辑器内(例如 Notepad), 那里列有在所选时间标记范围内的函数列表。通常,最 上端的五个函数值得注意。之后可用 IDE 来查看相关的 代码。
25
开发者资源 Symbian Developer Network http://developer.symbian.com
Symbian Developer Network newsletter http://developer.symbian.com/main/getstarted/newsletterarchive
Symbian OS Tools Providers http://developer.symbian.com/main/tools
Sony Ericsson Developer World http://developer.sonyericsson.com
Forum Nokia http://forum.nokia.com
Sun Microsystems Developer Services http://developer.java.sun.com
开发者意见调查 只要 5 分钟,您将能影响 Symbian 的未来 Symbian 想了解您对我们提供的开发者支持服务的意见。 我们汇编了一套问卷调查,我们希望通过此项调查来改 进我们的支持服务。 如果您想影响我们的决定,并获得赢取 Symbian 出版社 丛书的机会,请访问以下网站:
http://developer.symbian.com 并回答问卷。
Symbian 出版社新书推介
Symbian 出版社出版介绍 Symbian OS 各方面内容的书籍。 其中一些书目已推出中文版。
Symbian OS Platform Security 《Symbian OS 平台安全》(Symbian OS Platform Security) 一书深入揭示 了 Symbian OS 安全模式和 Symbian 签名等相关的 Symbian 方案。这些 内容对从 Symbian OS 许可持有商到 独立软件供应商等所有软件开发读 者来说都是至关重要的。
Accredited Symbian Developer Primer 《Symbian 认可开发伙伴初级读本》是 通过 ASD 认证考试及成为 Symbian 认 可开发伙伴的官方指南。该书由行内 权威专家著作,文笔简练、解释透彻。
Symbian 出版社:http://developer.symbian.com/main/academy/press
Also from
所有 Symbian OS C++ 开发者参考读物: Developing Software for Symbian OS Steve Babin 著 Symbian OS C++ for Mobile Phones – Volume 1 Richard Harrison 著 Symbian OS C++ for Mobile Phones – Volume 2 Richard Harrison 著 Symbian OS Explained Jo Stichbury 著 Symbian OS Internals Jane Sales 著
Also from
企业和 IT 专业人员参考读物: Rapid Mobile Enterprise Development for Symbian OS Ewan Spence 著
Symbian OS 项目经理参考读物: Symbian for Software Leaders David Wood 著
连接应用开发人员参考读物: Programming PC Connectivity Applications for Symbian OS Ian McDowall 著
Java 程序员参考读物: Programming Java 2 Micro Edition for Symbian OS Martin de Jode 著
Also from
已出版的小册子 编程诀窍 认证诀窍 新手入门 性能优化诀窍 UIQ 精要新手入门
汉译小册子 新手入门 编程诀窍 UIQ 精要新手入门
Essential Symbian OS
コーディング技法
Symbian OS 精要
性能优化诀窍 代码
度量
分析
改善
本书是开发人员必不可缺的工具,列出最常见性 能问题的起因,给出有价值的建议,帮助您在开 发中解决性能问题,并为高效软件设计提供实用 技巧。 性能提升技巧是《Symbian OS 精要》系列丛书 的一部分,旨在以便捷的形式为 Symbian OS 开 发人员提供有价值的信息。
Symbian 出版社 Symbian 出版社出版介绍 Symbian OS 及其技术 的书籍,其内容权威、实用,有针对性且不断 更新。有关 Symbian 出版社的情况可在 www.symbian.com/books 网站上查询。