早晨有个人要面试,正好有一些driver的优化工作想做,所以一早就来到公司。
没想到带了钥匙,门禁卡却忘记了带,踟蹰了一分钟,还是回去拿,来回浪费了22分钟。
坐在计算机前面,发现自己已经很久没有在周日上班了,比起三年前紧张程度降低了很多。以目前的工作进展来看,我还远不到可以松懈的时候,需要反省。
目前客户端表现为内存占用较多,而服务器端则是CPU开销较大。也就是说,要对driver同时针对空间和效率进行优化。总的来说,随着经验的丰富,我的设计方案执行效率是越来越低,资源占用则是越来越多,带来的好处是易用性和易维护性。目前看来,随着应用程度的加深,这方面的优化工作不能再忽略了。
就内存而言,我统计了一下driver使用内存的情况。客户端启动以后driver使用了100M内存,其中66M是脚本代码编译后的中间代码,而16M是值(计1M个),剩余的为其他(共享字符串,sqlite等第三方库,还没做详细统计),值一个为16字节(节省空间不大),所以先考虑节约编译后的中间代码。
目前第一阶段的工作是每个“程序”(即一个对象对应的program)都有一个符号表,占内存8K。这个表可以根据名字查询程序中相应函数的入口,供跨对象调用使用。因为系统存在大量的小对象,比如脚本、公式。它们基本只有2个函数,但是却使用一个符号表。那么1k个脚本、公式则用掉了8M空间(实际上会更多,因为一个表的符号节点也会占内存),但是实际发挥的作用很小。
基于这点,我有两个解决方案:
1. 增加一个压缩符号表的功能,生成代码段以后,根据符号表中实际符号的数量重新生成符号表
2. 增加快速函数表,生成代码段以后,删除符号表,生成一个可以查询函数入口的快表
第一个方案实现简单,但是有一个缺憾。首先,这个符号表每个符号节点需要申请一个小内存块,这就是为什么实际上一个符号表超过8K的缘故。一个脚本、公式对象目前只有两个函数,即需要两个符号节点,虽然单个量不多,但是整体也会消耗几百K的内存。
第二个方案实现起来会复杂一些,因为所有查询符号表的代码都要调整为查询快表 - 问题是全局对象不能查询快表,因为全局对象可以动态的增加新方法,不能释放符号表。另外,取消了符号表会导致根据名字查询对象变量的方法失效,必须增加相应方法。
最终我还是考虑用第二个方案实现。
同时,为了避免申请小块内存,快表在出现哈希冲突的时候,采用顺序后移找到空位填入的方法。因此,快表的大小需要比符号数量略多一点,这样在查找表中没有的符号时,不至于遍历所有条目才能知道未命中。
例子:
字符串, 函数编号
"create", 0
"main", 1
目前的符号表是2K条目(每项均为指针),其中计算字符串的哈希值,比如“create”是5,“main”是17,那么只有5 & 17这两个项有指针指向两个符号节点。如果采用小表,可以避免使用2K个条目,可以只用2个条目,那么“create”和“main”可能会冲突,都在第1号位置(5 % 2 = 1, 17 %2 = 1),此时采用链表进行连接这两个条目。
如果采用快表,则申请能包容三个表项的一张表,其中“create”会放置到5 % 3 = 2号位置,而“main”会放置到17 % 3 = 2,因为冲突顺移则到0号位置。查询时,“create”会直接命中,而“main”会检索一次后命中,其他则最多检索两次知道无法命中。
最后,为了避免浪费内存,所有的字符串均采用共享字符串。在查询比较时,也可以提高速度。
没有评论:
发表评论