阅读更多

12顶
0踩

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



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

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

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

A:
private final List<Bar> _bars;
for(Bar bar : _bars) {
    //Do important stuff
}

B:
private final List<Bar> _bars;
for(int i = 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):

优化前的代码:
HashMap<String,Foo> _map;
void addObjects(List<Foo> input)
{
  _map = new HashMap<String, Foo>();
  for(Foo f: input)
  {
    _map.put(f.getId(), f);
  }
}

优化后的代码
HashMap<String,Foo> _map;
void addObjects(List<Foo> input)
{
_map = new HashMap<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 这个属性了:
public class Foo {
private float _score;
private int _position;
private Bar _bar;
public int compareTo (Foo other) {
  return ComparisonChain.start().
  compare(_score, other.getScore()).
  compare(_position, other.getPosition()).
  compare(_bar.toString(), other.getBar().toString()).
  result;
}
}

但是上面这种实现方式总是会先生成两个 String 对象来保存 bar.toString() 和other.getBar().toString() 的值,即使这两个字符串的比较可能不需要。避免这样的开销,可以为Bar 对象实现一个 comparator:
public class Foo {
private float _score;
private int _position;
private Bar _bar;
private final BarComparator BAR_COMPARATOR = new BarComparator();
public int compareTo (Foo other) {
    return ComparisonChain.start().
    compare(_score, other.getScore()).
    compare(_position, other.getPosition()).
    compare(_bar, other.getBar(), BAR_COMPARATOR).
    result();
}
private static class BarComparator implements Comparator<Bar> {
@Override
    public int compare(Bar a, Bar b) {
    return a.toString().compareTo(b.toString());
}
}
}


4. 提前编译正则表达式

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

优化前:
private String transform(String term) {
    return outputTerm = term.replaceAll(_regex, _replacement);
}

优化后:
private final Pattern _pattern = Pattern.compile(_regex);
private String transform(String term) {
    String outputTerm = _pattern.matcher(term).replaceAll(_replacement);
}

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

将结果保存在缓存里也是一个避免过多开销的方法。但缓存只适用于在相同数据集撒花姑娘吗的相同数据操作(比如对一些配置的预处理或者一些字符串处理)。现在已经有多种LRU(Least Recently Used )缓存算法实现,但是Linkedin使用的是 Guava cache (具体原因见这里) 大致代码如下:
private final int MAX_ENTRIES = 1000;
private final LoadingCache<String, String> _cache;
// Initializing the cache
_cache = CacheBuilder.newBuilder().maximumSize(MAX_ENTRIES).build(new CacheLoader<String,String>() {
@Override
public String load(String key) throws Exception {
return expensiveOperationOn(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%。


  • 大小: 14.8 KB
  • 大小: 27.4 KB
来自: 码农网
12
0
评论 共 14 条 请登录后发表评论
14 楼 foreach4 2015-01-22 14:27
truekbcl 写道
dsjt 写道
truekbcl 写道
c++的这些东西完全没有消耗,花这么多心思去优化,还有什么理由不用c++?



因为文中提到的优化都是使用不合理,而不是因为语言本身性能差。
你用c++写个linkedlist然后用索引遍历,照样消耗大。


c++都会用迭代器,没有效率损耗。所以你用for或者foreach没有区别。
而且链表这样的结构,为什么要有get(i)这样的函数?这样的设计本身就是有问题的。

truekbcl 写道
dsjt 写道
truekbcl 写道
c++的这些东西完全没有消耗,花这么多心思去优化,还有什么理由不用c++?



因为文中提到的优化都是使用不合理,而不是因为语言本身性能差。
你用c++写个linkedlist然后用索引遍历,照样消耗大。


c++都会用迭代器,没有效率损耗。所以你用for或者foreach没有区别。
而且链表这样的结构,为什么要有get(i)这样的函数?这样的设计本身就是有问题的。

get(i) 方法没用? 你每次都要一遍遍的去遍历?
13 楼 scbzly_4223 2015-01-12 20:14
very good!
12 楼 fair_jm 2015-01-10 21:15
input.size() / 0.7
为什么不用两个构造参数的 size,1
设置因子为1呢?
11 楼 g21121 2015-01-08 15:34
for(int i = 0; i < _bars.size(); i++)
本身就可以优化为:
for(int i = 0,j=_bars.size(); i < j; i++)

这真是他们写的吗?
10 楼 white_crucifix 2014-12-22 16:22
hotapple 写道
white_crucifix 写道
嗯,java的fori 和 foreach 曾经做过简单的测试,的确fori要快上2到3倍的,可是人家就是喜欢foreach啊怎么办


试的是arraylist吧,那试试linkedlist,链表结构fori会慢很多。看RandomAccess接口说明。


嗯,毕竟链表的遍历是On2
9 楼 hotapple 2014-12-22 14:55
white_crucifix 写道
嗯,java的fori 和 foreach 曾经做过简单的测试,的确fori要快上2到3倍的,可是人家就是喜欢foreach啊怎么办


试的是arraylist吧,那试试linkedlist,链表结构fori会慢很多。看RandomAccess接口说明。
8 楼 truekbcl 2014-12-19 17:10
dsjt 写道
truekbcl 写道
c++的这些东西完全没有消耗,花这么多心思去优化,还有什么理由不用c++?



因为文中提到的优化都是使用不合理,而不是因为语言本身性能差。
你用c++写个linkedlist然后用索引遍历,照样消耗大。


c++都会用迭代器,没有效率损耗。所以你用for或者foreach没有区别。
而且链表这样的结构,为什么要有get(i)这样的函数?这样的设计本身就是有问题的。
7 楼 white_crucifix 2014-12-19 13:15
嗯,java的fori 和 foreach 曾经做过简单的测试,的确fori要快上2到3倍的,可是人家就是喜欢foreach啊怎么办
6 楼 houyujiangjun 2014-12-19 10:24
太细了.....  
5 楼 dsjt 2014-12-19 09:31
说为什么不用c++,
就好比网吧网管:重启就能解决问题,为什么不重启呢?
4 楼 dsjt 2014-12-19 09:30
truekbcl 写道
c++的这些东西完全没有消耗,花这么多心思去优化,还有什么理由不用c++?



因为文中提到的优化都是使用不合理,而不是因为语言本身性能差。
你用c++写个linkedlist然后用索引遍历,照样消耗大。
3 楼 truekbcl 2014-12-19 08:57
c++的这些东西完全没有消耗,花这么多心思去优化,还有什么理由不用c++?
2 楼 dsjt 2014-12-18 18:56
优化就应该针对耗时耗空间的地方。
1 楼 beck5859509 2014-12-18 18:25

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 软工考试——软件工程

    最后一个星期的复习重点理所应当放在了集中的考点上,所以我主要总结一下软件工程经常考的一些知识 考点分布(红色部分是考过的)

  • 软考中级,软件设计师考试那些内容,考试大纲什么的?

    (1)计算机与软件工程知识,考试时间为150分钟,笔试,选择题;(2)软件设计,考试时间为150分钟,笔试,问答题。上午综合知识选择题和下午软件设计题满分都是75分, 45分合格。软件设计师考试上午的综合知识选择题,覆盖的知识范围非常广,基本上涵盖了IT行业所需的大部分知识体系,包括硬件、操作系统、数据库、软件工程、面向对象等等。(1)掌握数据表示、算术和逻辑运算;(2)掌握相关的应用数学、离散数学的基础知识;(3)掌握计算机体系结构以及各主要部件的性能和基本工作原理;(4)掌握操作系统、程序设计语言的基础

  • 【STUDY | 高级软工】考试内容整理

    高级软件工程

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

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

  • Linkedin工程师是如何优化他们的Java代码的(转)

    英文原文:LinkedIn Feed: Faster with Less...这篇博文介绍了Linkedin信息流中间层Feed Mixer,它为Linkedin的Web主页,大学主页,公司主页以及客户端等多个分发渠道提供支撑(如下图所示)。  在Feed Mi...

  • java韩顺平最新教程,Java工程师进阶

    LinkedIn 内部的应用、Kafka 的主要设计目标以及为什么使用消息系统 第 2 章 Kafka 的架构:介绍 Kafka 的基本组成、拓扑结构及其内部的通信协议 第 3 章 Broker 概述:描述 Kafka 集群组成的基本元素 Broker Server...

  • 掌握P5级Java面试技巧

    HashMap底层原理,扩容机制,jdk8以后会使用红黑树优化?红黑树和二叉平衡树的区别,红黑树和B树,B+树的区别,Mysql二大引擎索引底层实现,HashMap在多线程环境中为何出错?ConcurrentHashMap底层实现,CAS,原子...

  • 首席工程师揭秘:LinkedIn大数据后台是如何运作的

    Jay Kreps是来自LinkedIn的首席工程师,他表示日志几乎在计算机产生的时候就存在,除了可用在分布式计算或者抽象分布式计算模型内部之外,还有广泛的用途。本文中他讲述的日志的原理和通过把日志用做单独服务来实现...

  • 风光储直流微电网Simulink仿真模型:光伏发电、风力发电与混合储能系统的协同运作及并网逆变器VSR的研究,风光储直流微电网Simulink仿真模型:MPPT控制、混合储能系统、VSR并网逆变器的设

    风光储直流微电网Simulink仿真模型:光伏发电、风力发电与混合储能系统的协同运作及并网逆变器VSR的研究,风光储直流微电网Simulink仿真模型:MPPT控制、混合储能系统、VSR并网逆变器的设计与实现,风光储、风光储并网直流微电网simulink仿真模型。 系统由光伏发电系统、风力发电系统、混合储能系统(可单独储能系统)、逆变器VSR?大电网构成。 光伏系统采用扰动观察法实现mppt控制,经过boost电路并入母线; 风机采用最佳叶尖速比实现mppt控制,风力发电系统中pmsg采用零d轴控制实现功率输出,通过三相电压型pwm变器整流并入母线; 混合储能由蓄电池和超级电容构成,通过双向DCDC变器并入母线,并采用低通滤波器实现功率分配,超级电容响应高频功率分量,蓄电池响应低频功率分量,有限抑制系统中功率波动,且符合储能的各自特性。 并网逆变器VSR采用PQ控制实现功率入网。 ,风光储; 直流微电网; simulink仿真模型; 光伏发电系统; 最佳叶尖速比控制; MPPT控制; Boost电路; 三相电压型PWM变换器;

  • 以下是针对初学者的 **51单片机入门教程**,内容涵盖基础概念、开发环境搭建、编程实践及常见应用示例,帮助你快速上手

    以下是针对初学者的 **51单片机入门教程**,内容涵盖基础概念、开发环境搭建、编程实践及常见应用示例,帮助你快速上手。

  • 【Python毕设】根据你提供的课程代码,自动排出可行课表,适用于西工大选课_pgj.zip

    【Python毕设】根据你提供的课程代码,自动排出可行课表,适用于西工大选课_pgj

  • 【毕业设计】[零食商贩]-基于vue全家桶+koa2+sequelize+mysql搭建的移动商城应用.zip

    【毕业设计】[零食商贩]-基于vue全家桶+koa2+sequelize+mysql搭建的移动商城应用

  • 电动汽车充电背景下的微电网谐波抑制策略与风力发电系统仿真研究,电动汽车充电微电网的谐波抑制策略与风力发电系统仿真研究,基于电动汽车充电的微电网谐波抑制策略研究,包括电动汽车充电负 载模型,风电模型,光

    电动汽车充电背景下的微电网谐波抑制策略与风力发电系统仿真研究,电动汽车充电微电网的谐波抑制策略与风力发电系统仿真研究,基于电动汽车充电的微电网谐波抑制策略研究,包括电动汽车充电负 载模型,风电模型,光伏发现系统,储能系统,以及谐波处理模块 风力发电系统仿真 ,电动汽车充电负载模型; 风电模型; 光伏发现系统; 储能系统; 谐波处理模块; 风力发电系统仿真,电动汽车充电微电网的谐波抑制策略研究:整合负载模型、风电模型与光伏储能系统

  • Vscode部署本地Deepseek的continue插件windows版本

    Vscode部署本地Deepseek的continue插件windows版本

  • 模拟电子学中滤波器的F0和Q参数详解及各类滤波器的设计方法

    内容概要:本文详细介绍了滤波器的两个关键参数——截止频率(F0)和品质因素(Q),并探讨了不同类型的滤波器(包括低通、高通、带通和带阻滤波器)的设计方法及其特性。文章首先明确了F0和Q的基本概念及其在滤波器性能中的作用,接着通过数学推导和图形展示的方式,解释了不同Q值对滤波器频率响应的影响。文中特别指出,通过调整Q值可以控制滤波器的峰谷效果和滚降速度,进而优化系统的滤波性能。此外,还讨论了不同类型滤波器的具体应用场景,如低通滤波器适用于消除高频噪声,高通滤波器用于去除直流分量和低频干扰,而带通滤波器和带阻滤波器分别用于选取特定频段信号和排除不需要的频段。最后,通过对具体案例的解析,帮助读者更好地理解和应用相关理论。 适合人群:电子工程及相关领域的技术人员、研究人员以及高校学生,特别是那些需要深入了解滤波器设计原理的人群。 使用场景及目标:适用于从事模拟电路设计的专业人士,尤其是希望掌握滤波器设计细节和技术的应用场合。目标是让读者能够灵活运用Q值和F0来优化滤波器设计,提升系统的信噪比和选择性,确保信号的纯净性和完整性。

  • QUARTEUSⅡ在EDA技术中的应用: CPLD/FPGA电路设计全流程解析与实例展示

    内容概要:本文主要讲述了利用QUARTUSⅡ进行电子设计自动化的具体步骤和实例操作,详细介绍了如何利用EDA技术在QUARTUSⅡ环境中设计并模拟下降沿D触发器的工作过程,重点探讨了系统规格设计、功能描述、设计处理、器件编译和测试四个步骤及相关的设计验证流程,如功能仿真、逻辑综合及时序仿真等内容,并通过具体的操作指南展示了电路设计的实际操作方法。此外还强调了QUARTUSⅡ作为一款集成了多种功能的综合平台的优势及其对于提高工作效率的重要性。 适用人群:电子工程、自动化等相关专业的学生或者工程师,尤其适用于初次接触EDA技术和QuartusⅡ的用户。 使用场景及目标:旨在帮助用户理解和掌握使用QUARTUSⅡ这一先进的EDA工具软件进行从概念设计到最后成品制作整个电路设计过程的方法和技巧。目标是在实际工作中能够熟练运用QUARTUSⅡ完成各类复杂电子系统的高效设计。 其他说明:文中通过具体的案例让读者更直观理解EDA设计理念和技术特点的同时也为进一步探索EDA领域的前沿课题打下了良好基础。此外它还提到了未来可能的发展方向,比如EDA工具的功能增强趋势等。

  • Simulink建模下的光储系统与IEEE33节点配电网的协同并网运行:光照强度变化下的储能系统优化策略与输出性能分析,Simulink模型下的光伏微网系统:光储协同,实现380v电压等级下的恒定功率

    Simulink建模下的光储系统与IEEE33节点配电网的协同并网运行:光照强度变化下的储能系统优化策略与输出性能分析,Simulink模型下的光伏微网系统:光储协同,实现380v电压等级下的恒定功率并网与平抑波动,Simulink含光伏的IEEE33节点配电网模型 微网,光储系统并网运行 光照强度发生改变时,储能可以有效配合光伏进行恒定功率并网,平抑波动,实现削峰填谷。 总的输出有功为270kw(图23) 无功为0 检验可以并网到电压等级为380v的电网上 逆变侧输出电压电流稳定(图4) ,Simulink; 含光伏; 配电网模型; 微网; 光储系统; 储能配合; 恒定功率并网; 电压等级; 逆变侧输出。,Simulink光伏微网模型:光储协同并网运行,实现功率稳定输出

  • 基于Andres ELeon新法的双馈风机次同步振荡抑制策略:附加阻尼控制(SDC)的实践与应用,双馈风机次同步振荡的抑制策略研究:基于转子侧附加阻尼控制(SDC)的应用与效能分析,双馈风机次同步振荡

    基于Andres ELeon新法的双馈风机次同步振荡抑制策略:附加阻尼控制(SDC)的实践与应用,双馈风机次同步振荡的抑制策略研究:基于转子侧附加阻尼控制(SDC)的应用与效能分析,双馈风机次同步振荡抑制策略(一) 含 基于转子侧附加阻尼控制(SDC)的双馈风机次同步振荡抑制,不懂就问, 附加阻尼控制 (SDC)被添加到 RSC 内部控制器的q轴输出中。 这种方法是由Andres ELeon在2016年提出的。 该方法由增益、超前滞后补偿器和带通滤波器组成。 采用实测的有功功率作为输入信号。 有关更多信息,你可以阅读 Andres ELeon 的lunwen。 附lunwen ,关键词:双馈风机、次同步振荡、抑制策略;转子侧附加阻尼控制(SDC);RSC内部控制器;Andres ELeon;增益;超前滞后补偿器;带通滤波器;实测有功功率。,双馈风机次同步振荡抑制技术:基于SDC与RSCq轴控制的策略研究

  • springboot疫情防控期间某村外出务工人员信息管理系统--.zip

    springboot疫情防控期间某村外出务工人员信息管理系统--

Global site tag (gtag.js) - Google Analytics