Symbian内存优化方案
1 概述
A项目在进行各种业务功能的变更追加删除以及查询时发现,真机上的耗时十分长,已经严重影响到了用户的使用。此问题的原因经调查与测验有以下几个方面:
- ?手机硬件系统受限
- ?复合查询合劣化OLite性能
- ?A项目业务以及部分控件的设计和算法不够精确
2问题分析
2.1??? 手机硬件系统受限
移动终端的硬件资源非常有限,曾经在服务器端的设计模式已经不再适用,在尽管做到算法精确的同时,应使用最少的系统资源。
2.2??? 复合查询合劣化OLite性能
经对试验数据的分析得知,手机端在对数据做多表复命查询时,其效率非常低下,采样片段为110毫秒(注:本文提到的时间匀为模拟器相对时间,实机约10倍以上)。实验将两麦数据按业务规则合并后,采用单表查询,测得耗时为16亳秒,总体性能提升是非常可观的,但并不是所有关联表都是可以按业务合并的,合迸规则为:
- ?必须为移动端只读访问表
- ?简单一对一或多对一的外键关系,合并至外键表,多对多,多对一不适用
2.3??? A项目业务以及部分控件的设计和算法不够精确
通过强化代码效率和重新封装高效率控件来解决,当前表现出来的主要为复杂的复合类空间性能问题。
3??? 解决方案
如何更经济合理高效的分配内存空间
动态分配内存是以内存操作效率损耗为代价,换来内存管理的灵活性和内存开销的经济性,当然了,从应用的技巧上市可以弥补这些缺陷的,从某种意义上讲,更好的估算出内存分配的初始值和增长粒度是使用动态内存分配方法高效的根本,即,减少内存重新分配和拷贝的次数。
3.1??? 动态分配内存空间
动态内存的分配方式有很多种,最为常用的是链表,不过由于Symbian系统中本身对内存操作已经封装好了,并且是基于连续的内存块进行操作的,其内存操作基类TDesC16/TDesC8的封装上毫无开放性可言,所有的函数居然全部是实体的,没有一个是virtual的函数,这使得通过重载而改变TDesC16/TDesC8类型的行为无法实现。
所以在Symbian当前的架构基础上实现动态内存分配只能采用非常原始低效而且简单的办法——拷贝。
当申请一段内存块的长度不足以满足动态追加的需求时,就需要根据当前长度和新增长度以及增长粒度算出一个新的内存块长度,使用new操作符重新申请一段足够长的内存空间,然后将原数据拷贝到新的内存当中,再追加新的数据,修改原始的堆描述符。
(具体实现方式参见ESMString)
3.2??? 自动变量的生存周期
动态内存分配问题已经解决,那么如何更好的管理动态分配的内存呢,这里有一个比较方便的办法,即使用一个从栈上分配的对象作为存根用于管理堆上分配的那一块动态内存,堆上空间的生存中期由栈上对象的生存周期控制,由于栈上对象的生存周期是由编译器自动控制的,进入作用域则分配,离开作用于则销毁。利用这个特性我们可以非常方便的控制一个堆上描述符的生存中期,即T类型构造的同时使用new操作符分配H类型,当T类型自动被析构的同时会使用delete操作符释放H类型。T类型对象就是所谓的自动变量。
3.3??? TString类的封装
值得庆幸的ESM中已经封装了一个基于栈上存根方式管理堆上资源的类型——TString,TString封装了基于动态内存操作的基本方法,当然如果需要可以使用其间接基类所提供的操作方法来控制TString中的数据,但是一般不允许使用其基类型进行会改变数据集长度的操作,主要运因在于其基类型没有更好的开放其操作方法给派生类,所以我们无法通过继承的方式改变其行为,这一点非常遗憾。
关于TString的实现细节方面如果有兴趣可以参考源代码。这里将不作更详细的说明。
3.4??? TString的异常清理与压栈时机
由于Symbian的异常清理回收机制非常弱,完全不同于正常的函数调用栈的回收.
3.4.1??? 正常的栈回收(函数反回,作用域退出等):
栈资源回收前会先将对象按压栈反序依次析构后将栈指针向栈底移动来释放空间。
3.4.2??? 异常时的栈内资源回收:
异常时的栈内资源回收则显得非常简陋,Symbian清理调用栈的时机居然是在捕获异常时才遍历当前栈,但当前栈数据类型已经面目全非了,无类型的数据只能硬性回收,直接将栈指针指向调用前位置,直接释放内存。
Symbian正常的栈空间释放不会引起TString的内存泄漏,因为其对象会被正确的析构后才被回收,而当声明TString以后,TString分配的堆上的资源,将会等待TString正确析构的同时被释放,但如果在这个过程中出现了异常("User::Leave()")的时候,Symbian这种对栈上对象及其不负责的做法会使所有类似于TString类型的对象出现无法回收的堆上空间——内存泄漏。
3.5??? 如何解决异常时内存泄漏问题
异常时回导致Symbian对栈内存的野蛮释放,导致TString保存的指针不能被删除,从而导致内存泄漏,这个问题也是可以解决的,这里引入了一个新的对象——自动回收代理(CClearAngent)它从CBase继承,目的是为了保证将他放入清理栈后可以正确的调用其析构函数,用以释放TString在堆上分配的内存空间,这个对象将会在TString创建的同时也被创建,CClearAngent对象采用new操作符从堆上分配,目的在于可以加入清理栈中,用以代理异常时堆上资源的回收。当然了,如果TString的整个生存周期中没有异常被抛出TString被正常的析构时,将会把TString构造时放入的CClearAngent指针原样的弹出,并安全销毁。
3.6??? 关于TString
本身是一个只包含堆上空间描述的值对象,自身由栈上分配,由编译运行时管理生命周期,而其自身生命周期又控制堆上资源的生命周期。异常时可以通过代理对象从清理栈释放堆上空间。
3.7??? 使用TString进行编程时注意的事项
- 严格控制栈上空间分配,不得分配大于64单元栈空间
- 严格控制递归函数深度
- 函数形参采用地址压栈方式传递
- 返回值采用地址压栈方式
- 内存分配尽可能使用从堆上动态变长对象
- TString抽象为TDesC16类型只能const方式使用,否则动态分配功能无效
- TString尽可能估算出长度和增长粒度,这样可以减少分配拷贝所带来的效率损耗
- 从控件类中取得大数据时请使用TString类型,而不是其基类型TDesC16,首先测试长度,再分配内存
- 采用自动清理类型的TString时注意其声明位置,尽量放在该变量作用域的昨开始位置。以保证清理栈的正确顺序
- TString初始化长度为0时,严禁进行以TDesC16的方式进行分配赋值操作
???????????? 例如:
????????????????? TString iIcon;??? // 错误
????????????????? iCoeEnv->ReadResource(iIcon, R_CMSENTRYVIEW_MBM);
????????????????? TString iIcon(128);??? // 正确
????????????????? iCoeEnv->ReadResource(iIcon, R_CMSENTRYVIEW_MBM);
3.8??? TString的几种正确用法
TString strActionName(E_ENABLE_CLEAR);// 异常时有代理释放内存,防止内存泄漏 strActionName = iActionsFix->At(i)->GetAction_name(); //=操作符可以动态分配内存。?
TString tempBuf(12);Esmedit->GetValue(tempBuf);void CEsmTextEditor::GetValue(TString & aText){ aText->Append(_L(“1234567890123”); //TString:: Append()方法支持动态分配}?
void CEsmTextEditor::GetValue(TString & aText){ // 测试长度,申请足够的空间 if (self->TextLength() >= aText.MaxLength()) { aText.SetMaxLength (self->TextLength() + 20); } self->GetText(aText);}?
TString sql(512) ;sql= "select a.EMPLOYEE_CODE, a.EMPLOYEE_NAME, a.EMPLOYEE_KANA, ";sql += " b.PASSWORD, b.Depart_code FROM EMPLOYEE a, EMP_DETAIL b ";sql += " WHERE a.EMPLOYEE_CODE = b.EMPLOYEE_CODE AND b.EMPLOYEE_CODE = ";sql += TString::ValueOf(aEmployeeid);?
{ TString tempBuf(256, E_ENABLE_CLEAR); // push1 TString warningInfo(E_ENABLE_CLEAR); // push2 iEikonEnv->ReadResource(tempBuf, R_MSG_CMSENTRYVIEW_MUST_ENTER_WARNING); warningInfo += tempBuf;warningInfo += _L("\n"); warningInfo += strErrorInfo; iEikonEnv->AlertWin(warningInfo); return EFalse; // pop1 // pop2 }?
TString testFormat(128, E_ENABLE_CLEAR); //分配足够的空间 testFormat.Format(_L("test%d, %d"), 32, 99);
?
3.9??? TString的几种错误用法
TString tempBuf; // 内存未分配 iEikonEnv->ReadResource(tempBuf, R_MSG_CMSENTRYVIEW_SELECT_PRODUCT_WARNING);?
TString tempBuf(12);Esmedit->GetValue(tempBuf);void CEsmTextEditor::GetValue(TDesC16 & aText) // TDesC16类型不能自动分配{ aText->Append(_L(“1234567890123”); // leagth=13}
?
TString tempBuf(12);Esmedit->GetValue(tempBuf);void CEsmTextEditor::GetValue(TDesC16 & aText) // TDesC16类型不能自动分配{ self->GetText(aText); // leagth=13}?
?
void CEsmTextEditor::GetValue(TString & aText) { self->GetText(aText); // GetText使用的TDesC16类型不能自动分配,可能会导致内存溢出}?
TString tempBuf(E_ENABLE_CLEAR); // 未分配内存 iEikonEnv->ReadResource(tempBuf, R_MSG_CMSENTRYVIEW_SELECT_PRODUCT_WARNING);?
?
TString testFormat(E_ENABLE_CLEAR); testFormat.Format(_L("test%d, %d"), 32, 99); //数组下标溢出
?
4??? TString 内存布局
?