RSS Feed

January, 2014

  1. 2013->2014

    January 30, 2014 by xudifsd

    2013年过得特别充实、刺激:三月面试工作,四月面试研究生并开始实习到九月开始读书。这期间基本上都不知道自己下一步应该怎么走,都是一步一步摸索过来的。幸好过得还算充实。

    在北京实习半年是我今年最开心的一段日子了,上班时有一群一起进公司的应届生陪着,周末还能和豆瓣的友邻面下基、听听讲座。还挺滋润。刚进公司时教练就要求新人每天开早会,说说新闻以及自己一天的计划,虽然很多时候我们都只是在聊天,但是通过这种方式认识了很多人,使得我们之间的交流很多,而且直接促成了我们分享的习惯:其中某人对某种技术特别熟悉就可以准备一下然后给大家讲解,而且很多时候完全不是特别正式的,聊着的时候提到,然后在大家都有空的时候就能分享。这种感觉很好。每天吃完中饭一起下去打球,黑ybb,打uno也是很开心的。

    只是这段日子并没有多长,过了半年就离开北京,开始了研究生生活。这生活比我想象中的忙很多倍,也比本科忙很多倍,编程作业特别多,还有项目要做,周末基本上没有休息时间,连刷豆瓣的频率都少了。幸好作业和项目都还算有意思,学到了很多,也积累了很多编码的经验。带项目的老师技术很牛,而且合作的同学也都很有激情。过得累但是快乐。

    今年也发现博客对我也越来越重要了,先是在公司面试时起了点作用,再是后来很多同学在第一次见面时都跟我说:我读过你的博客。看来当年买个主机并坚持写些东西还是很正确的决定啊。

    再过几天就新年了,希望能在新的一年完成学校的大项目,找个有意思的实习,能找到个妹纸就更棒了。也祝大家能在新年里出任总经理,迎娶白富美,走向人生巅峰。


  2. 记一次内存优化

    January 3, 2014 by xudifsd

    我们现在在做一个安卓应用的分析,需要读取apk文件,再通过工具分析成一个抽象语法树(以下简称为A树),之后再做一层和编译器相似的翻译,将A树翻译成我们的抽象语法树(以下简称为B树)。和java类似,apk中每个类都生成一个文件,而我们处理的单位也是这样的文件。为了模块化我们把分析和翻译分成两个独立步骤,所以分析函数和翻译函数的产出是一个装着各自结果的链表。

    现在为了验证树的正确性,我们写了个B树的输出,再打包回去运行。所以我们总共有三个分开的步骤:分析、翻译和输出。

    对于一些小型的apk这样做没什么问题,但前几天测一些大应用时会因为OOM挂掉,后来把jvm虚拟机堆大小设成2G才不会挂,拿工具看了下运行时的内存消耗峰值快达到1.5G,如下图

    优化前

    本来耗点内存也就算了,懒得调,不过之后我们准备批量测试apk,而跑测试的机子才2G内存,稍微跑大点的apk就挂可受不了。 之前也试了各种各样的优化方法,比如在翻译过程时破坏性地访问A树所在链表,翻译出一个B树就将相应的A树删除,或在链表没用后马上赋值为null。但是这种优化并没有带来很多效果,内存消耗还是很大,后来有人说应该将这两个独立步骤合并,因为我们现在的目的只是输出B树,所以可以写一个独立的函数,一次一个地分析出A树并马上翻译成B树再输出,这样内存中同时存在的树最多就两个文件产生的树,而不是之前的一个项目所有文件产生的树,内存消耗就会大大减少。

    但是总觉得这样的做法并不优雅:需要维护两个内容基本相同的模块,而且目的变了后之前代码完全没法复用。最好的方法就是在不增加冗余代码并且不修改大体框架的情况下实现这种优化。

    昨天晚上又看了看整个框架,发现这种流程不就是lisp里面的map么:先以解析函数和文件的链表作为参数传给map,将翻译函数传给map再过一遍之前的输出,之后把输出函数传给map再过一遍前面的输出。

    想到map我就想到了之前被clojure的惰性map坑过一次:将函数传给map,之后一直等它输出却没有任何反应,调了很久才发现原来是惰性的问题。突然发现惰性求值不就是我们需要的么:可以将解析函数处理成惰性的,把翻译函数也处理成惰性的,在输出阶段再打破这种惰性就行了,因为只有到了输出阶段才真正地需要树的结构。这样一来我们之前的架构也可以在基本不修改的情况下使用这样的优化,更妙的是这样的架构之后也能复用,只有在真正需要树的情况下再打破这种惰性即可,这样不管以后的目的如何都能保证在内存中不会完整保留整个项目的树结构,除非真正用到整个树,即使访问了整个树也可以根据需求释放内存,像输出的这种情况,输出后就将对应的树删除即可。

    优化点想到后实现了下,删除了100多行,增加了60多行就实现了,而且效果惊人,内存使用的峰值从1.5G下降到500M。如下图

    优化后

    实现之前还想自己写个接口做这种惰性求值,后来发现Callable和Runnable接口就能直接拿来干这事,打破惰性时还能直接扔给ExecutorService多线程跑。

    这是迄今为止我做优化最成功的一次了,不仅效果惊人而且还挺优雅。以前做优化都什么效果,有的优化后甚至还差了。看来以后还是得真正了解到优化点并且有比较优雅的解决方案时再着手优化,否则不仅效果差而且最后丑到自己都不想看了。