最近在刷各大公司的技术博客的时候,我在Linkedin的技术博客发现了一篇很不错博文。这篇博文介绍了Linkedin信息流中间层Feed Mixer,它为Linkedin的Web主页,大学主页,公司主页以及客户端等多个分发渠道提供支撑(如下图所示)。
AD:
最近在刷各大公司的技术博客的时候,我在Linkedin的技术博客发现了一篇很不错博文。这篇博文介绍了Linkedin信息流中间层Feed Mixer,它为Linkedin的Web主页,大学主页,公司主页以及客户端等多个分发渠道提供支撑(如下图所示)。
代码A执行的时候会为这个抽象列表创建一个迭代器,而代码B就直接使用get(i)来获取元素,相对于代码A省去了迭代器的开销。
实际上这里还是需要一些权衡的。代码A使用了迭代器,了在获取元素的时候的时间复杂度是O(1)(使用了getNext()和hasNext()方法),最终的时间复杂度为O(n)。但是对于代码B,循环里每次在调用_bars.get(i)的时候花费的时间复杂度为O(n)(假设这个list为一个 LinkedList),那么最终代码B整个循环的时间复杂度就是O(n^2)(但如果代码B里面的list是ArrayList,那get(i)方法的时间复杂度就是O(1)了)。所以在决定使用哪一种遍历的方式的时候,我们需要考虑列表的底层实现,列表的平均长度以及所使用的内存。最后因为我们需要优化内存,再加上ArrayList在大多数情况下查找的时间复杂度为O(1),最后决定选择代码B所使用的方法。
2.在初始化的时候预估集合的大小
从Java的这篇文档我们可以了解到:“一个HashMap 实例有两个影响它性能的因素:初始大小和加载因子(load ctor)。 [] 当哈希表的大小达到初始大小和加载因子的乘积的时候,哈希表会进行 rehash操作 [] 如果在一个HashMap 实例里面要存储多个映射关系时,我们需要设置足够大的初始化大小以便更有效地存储映射关系而不是让哈希表自动增长让后rehash,造成性能瓶颈。”
在Linkedin实践的时候,常常碰到需要遍历一个ArrayList并将这些元素保存到HashMap里面去。将这个HashMap初始化预期的大小可以避免再次哈希所带来的开销。初始化大小可以设置为输入的数组大小除以默认加载因子的结果值(这里取0.7):
优化前的代码:
3. 延迟表达式的计算
在Java中,所有的方法参数会在方法调用之前,只要有方法参数是一个表达式的都会先这个表达式进行计算(从左到右)。这个规则会导致一些不必要的操作。考虑到下面一个场景:使用ComparisonChain比较两个Foo对象。使用这样的比较链条的一个好处就是在比较的过程中只要一个 compareTo 方法返回了一个非零值整个比较就结束了,避免了许多无谓的比较。例如现在这个场景中的要比较的对象最先考虑他们的score, 然后是 position, 最后就是_bar这个属性了:
但是这种实现方式总是会先生成两个String对象来保存bar.toString()和other.getBar().toString()的值,即使这两个字符串的比较可能不需要。避免这样的开销,可以为Bar 对象实现一个 comparator:
4. 提前编译正则表达式
字符串的操作在Java中算是开销比较大的操作。还好Java提供了一些工具让正则表达式尽可能地高效。动态的正则表达式在实践中比较少见。在接下来要举的例子中,每次调用String.replaceAll()都包含了一个常量模式应用到输入值中去。因此我们预先编译这个模式可以节省CPU和内存的开销。
优化前:
5. 尽可能地缓存Cache it if you can
将结果保存在缓存里也是一个避免过多开销的方法。但缓存只适用于在相同数据集撒花姑娘吗的相同数据操作(比如对一些配置的预处理或者一些字符串处理)。现在已经有多种LRU(Least Recently Used )缓存算法实现,但是Linkedin使用的是Guavacache (具体原因见这里) 大致代码如下:
网友评论 ()条 查看