最近在刷各大公司的技术博客的时候,我在Linkedin的技术博客上面发现了一篇很不错博文。这篇博文介绍了Linkedin信息流中间层Feed Mixer,它为Linkedin的Web主页,大学主页,公司主页以及客户端等多个分发渠道提供支撑(如下图所示)。

在Feed Mixer里面用到了一个叫做SPR(念“super”)的库。博文讲的就是如何优化SPR的java代码。下面就是他们总结的优化经验。

1. 谨慎对待Java的循环遍历

Java中的列表遍历可比它看起来要麻烦多了。就以下面两段代码为例:

  • A:
1
2
3
4
privatefinal List<Bar> _bars;
for(Bar bar : _bars) {
    //Do important stuff
}

  • B:
1
2
3
4
5
privatefinal List<Bar> _bars;
for(inti = 0; i < _bars.size(); i++) {
Bar bar = _bars.get(i);
//Do important stuff
}

代码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 factor)。 […] 当哈希表的大小达到初始大小和加载因子的乘积的时候,哈希表会进行 rehash操作 […] 如果在一个HashMap 实例里面要存储多个映射关系时,我们需要设置足够大的初始化大小以便更有效地存储映射关系而不是让哈希表自动增长让后rehash,造成性能瓶颈。”

在Linkedin实践的时候,常常碰到需要遍历一个 ArrayList 并将这些元素保存到 HashMap 里面去。将这个 HashMap 初始化预期的大小可以避免再次哈希所带来的开销。初始化大小可以设置为输入的数组大小除以默认加载因子的结果值(这里取0.7):

  • 优化前的代码:
1
2
3
4
5
6
7
8
9
HashMap<String,Foo> _map;
voidaddObjects(List<Foo> input)
{
  _map = newHashMap<String, Foo>();
  for(Foo f: input)
  {
    _map.put(f.getId(), f);
  }
}

  • 优化后的代码
1
2
3
4
5
6
7
8
9
HashMap<String,Foo> _map;
voidaddObjects(List<Foo> input)
{
_map = newHashMap<String, Foo>((int)Math.ceil(input.size() / 0.7));
for(Foo f: input)
{
_map.put(f.getId(), f);
}
}

3. 延迟表达式的计算

在Java中,所有的方法参数会在方法调用之前,只要有方法参数是一个表达式的都会先这个表达式进行计算(从左到右)。这个规则会导致一些不必要的操作。考虑到下面一个场景:使用ComparisonChain比较两个 Foo 对象。使用这样的比较链条的一个好处就是在比较的过程中只要一个 compareTo 方法返回了一个非零值整个比较就结束了,避免了许多无谓的比较。例如现在这个场景中的要比较的对象最先考虑他们的score, 然后是 position, 最后就是 _bar 这个属性了:

1
2
3
4
5
6
7
8
9
10
11
12
13
publicclass Foo {
privatefloat _score;
privateint _position;
privateBar _bar;
  
publicint compareTo (Foo other) {
returnComparisonChain.start().
compare(_score, other.getScore()).
compare(_position, other.getPosition()).
compare(_bar.toString(), other.getBar().toString()).
result;
}
}

但是上面这种实现方式总是会先生成两个 String 对象来保存 bar.toString() 和other.getBar().toString() 的值,即使这两个字符串的比较可能不需要。避免这样的开销,可以为Bar 对象实现一个 comparator:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
publicclass Foo {
privatefloat _score;
privateint _position;
privateBar _bar;
privatefinal BarComparator BAR_COMPARATOR = newBarComparator();
  
publicint compareTo (Foo other) {
returnComparisonChain.start().
compare(_score, other.getScore()).
compare(_position, other.getPosition()).
compare(_bar, other.getBar(), BAR_COMPARATOR).
result();
}
privatestatic class BarComparator implementsComparator<Bar> {
@Override
publicint compare(Bar a, Bar b) {
returna.toString().compareTo(b.toString());
}
}
}

4. 提前编译正则表达式

字符串的操作在Java中算是开销比较大的操作。还好Java提供了一些工具让正则表达式尽可能地高效。动态的正则表达式在实践中比较少见。在接下来要举的例子中,每次调用String.replaceAll() 都包含了一个常量模式应用到输入值中去。因此我们预先编译这个模式可以节省CPU和内存的开销。

  • 优化前:
1
2
3
privateString transform(String term) {
returnoutputTerm = term.replaceAll(_regex, _replacement);
}

  • 优化后:
1
2
3
4
privatefinal Pattern _pattern = Pattern.compile(_regex);
privateString transform(Stringterm) {
StringoutputTerm = _pattern.matcher(term).replaceAll(_replacement);
}

5. 尽可能地缓存Cache it if you can

将结果保存在缓存里也是一个避免过多开销的方法。但缓存只适用于在相同数据集撒花姑娘吗的相同数据操作(比如对一些配置的预处理或者一些字符串处理)。现在已经有多种LRU(Least Recently Used )缓存算法实现,但是Linkedin使用的是 Guava cache (具体原因见这里) 大致代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
privatefinal int MAX_ENTRIES = 1000;
privatefinal LoadingCache<String, String> _cache;
// Initializing the cache
_cache = CacheBuilder.newBuilder().maximumSize(MAX_ENTRIES).build(newCacheLoader<String,String>() {
@Override
publicString load(String key) throwsException {
returnexpensiveOperationOn(key);
}
}
);
  
//Using the cache
String output = _cache.getUnchecked(input);

6. String的intern方法有用,但是也有危险

String 的 intern 特性有时候可以代替缓存来使用。

从这篇文档,我们可以知道:
“A pool of strings, initially empty, is maintained privately by the class String. When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned”.

这个特性跟缓存很类似,但有一个限制,你不能设置最多可容纳的元素数目。因此,如果这些intern的字符串没有限制(比如字符串代表着一些唯一的id),那么它会让内存占用飞速增长。Linkedin曾经在这上面栽过跟头——当时是对一些键值使用intern方法,线下模拟的时候一切正常,但一旦部署上线,系统的内存占用一下就升上去了(因为大量唯一的字符串被intern了)。所以最后Linkedin选择使用 LRU 缓存,这样可以限制最大元素数目。

最终结果

SPR的内存占用减少了75%,进而将feed-mixer的内存占用减少了 50% (如下图所示)。这些优化减少了对象的生成,进而减少了GC得频率,整个服务的延迟就减少了25%。

52

from: http://www.importnew.com/14588.html

Linkedin工程师是如何优化他们的Java代码的相关推荐

  1. Linkedin 工程师如何优化他们的 Java 代码

    最近在刷各大公司的技术博客的时候,我在Linkedin的技术博客上面发现了一篇很不错博文.这篇博文介绍了Linkedin信息流中间层Feed Mixer,它为Linkedin的Web主页,大学主页,公 ...

  2. java+set+split_阿里资深工程师教你如何优化 Java 代码!

    原标题:阿里资深工程师教你如何优化 Java 代码! 作者 | 王超 责编 | 伍杏玲 明代王阳明先生在<传习录>谈为学之道时说: 私欲日生,如地上尘,一日不扫,便又有一层.着实用功,便见 ...

  3. asp java 变量_ASP_用stack变量优化Java代码,  java程序包含了大量的对象 - phpStudy...

    用stack变量优化Java代码 java程序包含了大量的对象,我们需要了解它们是从哪里被访问的,变量存储于何处对程序的性能有显著的影响--尤其是某些需要被频繁访问的变量. 我们写一个Java类,在其 ...

  4. 从Java代码到Java堆理解和优化您的应用程序的内存使用

    从Java代码到Java堆理解和优化您的应用程序的内存使用 简介: 本文将为您提供 Java? 代码内存使用情况的深入见解,包括将 int 值置入一个Integer 对象的内存开销.对象委托的成本和不 ...

  5. java代码统计收藏量_干货收藏 | 35个Java 代码性能优化总结(上)

    原标题:干货收藏 | 35个Java 代码性能优化总结(上) 前言 代码优化,一个很重要的课题.可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这 ...

  6. Android 性能优化 之谈谈Java内存区域

    最近一年副业主要在学习投资和技能学习,把以前学习内存分析的一些笔记总结发出来,写了很多笔记总结都没有写完就又忙着了,最近再次总结复习学习一遍,还有提醒各位同学一定要学会投资.. 了解Android 内 ...

  7. java代码优化_java代码之美(11)---java代码的优化

    java代码的优化 随着自己做开发时间的增长,越来越理解雷布斯说的: 敲代码要像写诗一样美.也能理解有一次面试官问我你对代码有洁癖吗? 一段好的代码会让人看就像诗一样,也像一个干净房间会让人看去很舒服 ...

  8. 44个Java代码性能优化总结

    转载自 44个Java代码性能优化总结 代码优化的最重要的作用应该是:避免未知的错误.在代码上线运行的过程中,往往会出现很多我们意想不到的错误,因为线上环境和开发环境是非常不同的,错误定位到最后往往是 ...

  9. Android 性能优化 之初识Java内存区域

    前言     不管是日常开发,还是面试,我们都会和内存打交道,因为内存是支持软件运行的基本硬件环境.了解Android 内存管理,就有必要了解Java GC机制,必须要理解JVM 内存区域,这里我们主 ...

最新文章

  1. 13道BGP属性面试题
  2. 【深度学习】从零开始 Mask RCNN 实战:基于 Win10 + Anaconda 的 Mask RCNN 环境搭建
  3. java table修改指定行_(变强、变秃)Java从零开始之JQuery购物车功能实操
  4. PHP的break与continue
  5. 我的天,强化学习还能用在自动驾驶领域?
  6. [html] 百度、淘宝、京东移动端首页秒开是如何做到的?
  7. JSLint检测Javascript语法规范
  8. 2017百度之星复赛:1006. Valley Numer(数位DP)
  9. Silverlight 功能其全的PDF 控件
  10. 可能是比原版Firefox更好用的个人定制版Firefox
  11. 去除桌面图标蓝底的方法
  12. sql grop by having order by 的用法
  13. 百度无线音乐盒刷打印服务器,百度无线音乐盒引关注 让普通音箱秒变Wi-Fi音箱...
  14. 【C++000】ASCII码转换公式(大小写转换)
  15. 海贼王:第86话 (希鲁鲁克的樱花与被继承的意志!)
  16. 2012IT公司校园招聘笔试面试题
  17. 于氏:简单监控全网服务
  18. JDK的安装和环境变量配置-Win10系统
  19. 【开源项目】之智能婴儿摇篮
  20. 为何从游戏剧本的角度看,《最后生还者2》的体验并不好?

热门文章

  1. 云宇宙iwemeta: 亚马逊全面进军元宇宙
  2. 专访小米欧阳辰:深度揭秘小米广告平台的构建、底层模块和坑
  3. 用RPython在云端运行可扩展数据科学
  4. quartz mysql 建表_quartz创建表语句
  5. 四、【线性表】线性表的顺序表示和实现
  6. MySQL - 索引优化案例实操
  7. Redis进阶-Redis使用建议一二事
  8. MySQL-数据库监控初探
  9. 学习笔记——Numpy基本操作(一)
  10. MUI+H5手机上传照片 支持多图片上传和拍照上传