这波性能优化,太炸裂了!(上)

简介: 这波性能优化,太炸裂了!(上)

不是,这不是我。我还年轻,也比他帅。

这是今天文章的主人公。

他叫做 Brett Wooldridge,你应该是不认识的。

但是我把他的 github 截图给你看看,你一定知道他写的开源项目:

image.png

看到了吗?


image.png


他就是大名鼎鼎的 HikariCP 的爸爸啊。

而且你看他的 github 的简介,写的很有感觉:

Father of an angel who fell to Earth and somehow into my life.

一个落到地球上的天使的父亲,她不知不觉地进入了我的生活。

图片应该是就他的孩子,他在旁边露出了老父亲般慈祥的微笑。

文章最开始的那一张图,是我从这个报道中找到的:

https://blog.jooq.org/2017/02/21/jooq-tuesdays-brett-wooldridge-shows-what-it-takes-to-write-the-fastest-java-connection-pool/

其中问的第一个问题是这个:

image.png


你创建了Java 中最流行的连接池之一,HikariCP。那么请问是什么让你的库如此受欢迎呢?

下面一小节,我就用第一人称的角度给大家讲述一下这个老哥是怎么回答这个问题的。


为啥写 HikariCP?


啥,你问我为啥要写 HikariCP?

哎呀妈呀,还不是因为没有找到趁手的家伙什嘛。

我几年前写代码的时候,需要用到一个数据库的连接池,于是就像大多数开发者一样,面向浏览器编程,在网上找了一个开源的池子,拿来就用。

你还别说,看起来好像还不错。

但是后来在对项目做性能测试的时候,就慢慢的发现这个池子不太行了,老是会碰到死锁、连接状态不正确的问题。

我寻思这玩意不是坑爹吗?

image.png


但是当时用的连接池是开源的嘛,本着开源的精神,我就想着把代码拉下来看看嘛,能不能帮忙给修复一下。

结果我打开代码的时候,好家伙,代码量之大,至少比我预期的要多出个几千行来。

代码多就算了,忍忍就能读下去。

神奇的是代码逻辑。

我是去排查死锁问题的,结果我发现锁是一个套一个。

有的时候在一个方法里面获取到锁了,我硬是找不到释放的地方。

最后在隔着十万八千里的地方,看到了释放锁的地方。

我当时大概是这样的:


image.png


因为我知道,我已经没有办法找到死锁潜伏在代码的哪个角落了。

就算我解决了当前的问题,按照项目这样的写法,迟早也会碰到其他的问题。

于是我当机立断,决定...

在网上再找一个。


image.png


这次我学乖了,找到新的连接池之后,我先看了它的代码。

因为被死锁搞怕了,所以特别是关注了关于锁的部分。

新找到的连接池锁的语义确实更清晰了,但是代码量仍然是我预期的二倍多。

除此之外,我研究过的所有的链接池,都在以各种各样的方式违反 JDBC 的合约。

比如,我发现的最常见的一个问题是这样的。

当一个链接被用完了,放回池子里面的时候。某些池子并没有把这个链接里面的消息清理干净,比如自动提交、事务隔离级别等等,导致下个消费者再次拿到这个链接的时候,是一个“脏”链接。

我当时就在想:


image.png


Really?这就是 Java 生态中的连接池的现状?不行了,我要亲自出手了。于是出于需要和挫折感,我创建了HikariCP。

回到最开始的问题。

如上所述,在我写 HikariCP 之前,其实已经有很多成熟的连接池了,那么 HikariCP 是如何变得流行的呢?

再我看来,如果我主打正确性和可靠性,其实不算一个好的卖点,因为我认为这是必须所具备的东西。

所以我专注于推广性能。在我的各个社交媒体上去进行推广。

在 2015 年的某个时候,Wix 工程团队写了一篇关于使用 HikariCP 的博客。

这一波我就直接弹射起飞了。HikariCP 也算是走进了大家的视野。

最后,我确实希望随着时间的推移,更多的用户会对正确性和可靠性给予同等的重视,没有这些性能就没有意义。

就我而言,我打算多写一些关于 HikariCP 的这些方面的文章。


性能为啥牛逼?


前面说了,HikariCP 的卖点是强悍的性能。

那么它的性能为啥这么牛逼呢?

其实答案就写在 HikariCP 的 github 主页上:

https://github.com/brettwooldridge/HikariCP



image.png


在进入 how we do it here 之前,先简单说说这个项目的名称。

可以看到一个大大汉字:光。

关于这个名字的来源,其实在前面提到的报道中也提到过:

image.png

HikariCP,被翻译为“光”,在英语中,在HikariCP的上下文中,它是一个双关语。在这个项目里面,"光"不只表示速度快,也是指代码量很少。

Hikari 的发音是 Hi-ka-lee。

这个大家记一下。

我记得有一次面试,有个面试者提到了这个连接池,但是他不知道怎么读。

他说:就是 H 开通 CP 结尾的那个连接池,咋读的我忘记了。

但是我当时也一下就反应过来了。

我说:嗯,我知道你说的哪个连接池,你继续说。

其实,当时我也不知道怎么读,就很尴尬。

好了,接下来,就一起看看性能为啥这么牛逼。

答案,作者都在 github 里面写着呢:

https://github.com/brettwooldridge/HikariCP/wiki/Down-the-Rabbit-Hole

首先,这个文章的标题就很有意思:


image.png

image.png

哦,down the rabbit hole 原来是是冒险进入未知世界的隐喻。出自著名的《爱丽丝梦游仙境》一书中。

一般我们用 down the rabbit hole 用来描述陷入一个愈发奇怪、令人摸不着头脑或出人意料的状况,而且一件事情促使另一件事情的发生,接连不断,因此越陷越深、无从脱身的场景。

一个英语的小俚语,送给大家。

image.png

知道标题的含义后,等你看完作者写的文章之后,你再次审视这个“兔子洞”的标题,你就会发现:真特么贴切啊。

全文读完,理解之后,我发现作者想表达的为什么这么快的原因有四个:

  • 字节码级别的优化-尽量的利用 JIT 的内联手段
  • 字节码级别的优化-利用更容易被 JVM 优化的指令
  • 代码级别的优化-利用改造后的 FastList 代替 ArrayList
  • 代码级别的优化-利用无锁的 ConcurrentBag

我们一个个的看。


字节码级别的优化


文章的开头,作者就说了:我这波操作在字节码,就问你牛不牛逼。

image.png

简单的翻译一下关键的地方:

  • 为了使 HikariCP 变得更快,我进行了字节码级别的优化。
  • 我拿出了我所知道的所有技巧来利用 JIT 优化,从而帮助到你。
  • 我研究了编译器的字节码输出,甚至是JIT的汇编输出,以限制关键的程序小于 JIT 的 inline-threshold。

这个地方作者提到了 JIT 的内联优化。

啥是内联?

内联其实是一个动作。

选定某个被调用的方法,将其内容复制到被调用的地方。

举个简单的例子,假设代码是这样的:

int result = add(a,b);
private int add(int x,int y){
    return x+y;
}

那么经过 JIT 的内联优化之后,代码就会变成这样:

int result= a + b;

这样,节约了调用 add 方法的开销。

内联,也被称为优化子母,它为其他的优化手段建立了非常好的基础,所以除了上面写的那个例子之外,还有很多的更加进阶的体现方式,比如逃逸分析、循环展开、锁消除:

image.png

那么一个调用的开销到底有哪些呢?

我想无外乎就是这几步:

  • 首先要设置方法调用需要传递的参数,对吧?
  • 有了参数,是不是还得查询具体调用哪个方法,对吧?
  • 然后如果有类似于局部变量,或者求值这样的方法,还得创建新的调用栈帧,创建新的运行时数据结构,对吧?
  • 最后,还有可能需要给调用方返回一个结果,对吧?

有的朋友就就会说了,至于吗?这个开销看着也不大啊?

是的,确实不大,但是当再小的一个优化点,乘以一个巨大的调用量之后,最终的结果都是很可观的。

我想这个道理大家都明白。

作者也在文章里面说了:

image.png

HikariCP 包含了许多微观的优化,这些优化单独来看几乎无法衡量,但结合起来就能提升整体性能。

甚至在数百万次的调用中,优化的级别是以毫秒的时间来衡量的。

可能这就是大佬吧。

我想,追求性能的极致,也就不过如此了。

接下来,说说另外一个字节码级别的优化:

invokevirtual vs invokestatic

这波优化我觉得简直就是在大气层了。

作者举了个例子。

之前获取 Connection, Statement, ResultSet 的代理对象什么的都是通过单例工厂方法。

就类似于这样的:

image.png

目录
相关文章
|
3月前
|
JSON C# 开发者
💡探索C#语言进化论:揭秘.NET开发效率飙升的秘密武器💼
【8月更文挑战第28天】C#语言凭借其强大的功能与易用性深受开发者喜爱。伴随.NET平台演进,C#持续引入新特性,如C# 7.0的模式匹配,让处理复杂数据结构更直观简洁;C# 8.0的异步流则使异步编程更灵活高效,无需一次性加载全部数据至内存。通过示例展示了模式匹配简化JSON解析及异步流实现文件逐行读取的应用。此外,C# 8.0还提供了默认接口成员和可空引用类型等特性,进一步提高.NET开发效率与代码可维护性。随着C#的发展,未来的.NET开发将更加高效便捷。
56 1
|
3月前
|
缓存 算法 数据库
安卓应用性能优化:一场颠覆平凡的极限挑战,拯救卡顿的惊世之战!
【8月更文挑战第7天】《安卓应用性能优化实战》
49 4
|
3月前
|
Java 数据库连接 缓存
Hibernate性能调优:五大秘籍,让应用效能飙升,告别慢如蜗牛的加载,体验丝滑般流畅!
【8月更文挑战第31天】本文深入探讨了提升Hibernate应用性能的五大技巧,包括选择合适的缓存策略、优化查询语句、合理使用Eager与Lazy加载、批量操作与事务管理以及利用索引和数据库优化。通过正确配置多级缓存、分页查询、延迟加载、批量处理及合理创建索引,能够显著提高应用响应速度与吞吐量,改善用户体验。这些技巧需根据具体应用场景灵活调整,以实现最佳性能优化效果。
139 0
|
3月前
|
Rust 搜索推荐 测试技术
揭秘Rust性能极限!从菜鸟到高手的蜕变之路:深入剖析性能分析与调优的隐秘技巧
【8月更文挑战第31天】Rust凭借卓越的性能、内存安全性和并发支持,成为高性能系统开发的首选语言。本文详细介绍Rust的性能优化流程,涵盖从基础分析到高级调优的技巧,并通过示例代码展示具体操作。内容包括理解Rust的性能优势、常用性能分析工具(如Cargo Bench、Valgrind和perf)、基准测试示例以及优化技巧,如减少内存分配、利用并发模型、优化数据结构和避免过度抽象。通过持续优化与迭代,开发者可充分发挥Rust的潜力,提升程序性能。
108 0
|
3月前
|
开发者 CDN 监控
【破局·提速】当Vaadin遇上性能怪圈:开发者的智慧较量与极速加载的实战秘籍!
【8月更文挑战第31天】本文详细介绍了优化Vaadin应用性能的方法,特别是提高加载速度的实战技巧。首先分析性能瓶颈,如服务器响应时间和数据库查询效率等;然后通过代码优化、数据分页与急切加载技术减少资源消耗;接着利用资源压缩合并及CDN加速,进一步提升加载速度;最后通过持续性能监控和测试确保优化效果。通过综合应用这些策略,可显著改善用户体验。
75 0
|
3月前
|
开发者 缓存 数据库
【性能奇迹】Wicket应用的极速重生:揭秘那些让开发者心跳加速的调优秘技!
【8月更文挑战第31天】在软件开发中,性能优化是确保应用快速响应和高效运行的关键。本书《性能调优:Apache Wicket应用的速度提升秘籍》详细介绍了如何优化Apache Wicket应用,包括代码优化、资源管理、数据库查询优化、缓存策略及服务器配置等方面。通过减少不必要的组件渲染、优化SQL查询、使用缓存和调整服务器设置等方法,本书帮助开发者显著提升Wicket应用的性能,确保其在高并发和数据密集型场景下的稳定性和响应速度。
41 0
|
存储 缓存 运维
完爆 90% 的性能毛病,22 点通用绝招介绍(二)
完爆 90% 的性能毛病,22 点通用绝招介绍(二)
完爆 90% 的性能毛病,22 点通用绝招介绍(二)
|
存储 NoSQL 算法
完爆 90% 的性能毛病,22 点通用绝招介绍(一)
完爆 90% 的性能毛病,22 点通用绝招介绍(一)
完爆 90% 的性能毛病,22 点通用绝招介绍(一)
|
Java C++
这波性能优化,太炸裂了!(中)
这波性能优化,太炸裂了!(中)
146 0
这波性能优化,太炸裂了!(中)