从源码里的一个注释,我追溯到了12年前,有点意思。 (中)

简介: 从源码里的一个注释,我追溯到了12年前,有点意思。 (中)

在 JVM 中,String 是一个非常重要的类,这种微小的优化可能会提高一点启动速度。另一方面,BigInteger 对于 JVM 的启动并不重要。

所以,如果你看了这篇文章,自己也想在代码里面用这样的“棒”写法,三思。

醒醒吧,你才几个流量呀,值得你优化到这个程度?

image.png

而且,我就告诉你,前面字节码层面是有优化不假,我们都眼见为实了。

但是这个老哥提醒了我:


image.png

他提到了 JIT,是这样说的:这些微小的优化通常是不必要的,这只是减少了方法的字节码大小,一旦代码变得足够热而被 JIT 优化,它并不真正影响最终生成的汇编。

于是,我在 stackoverflow 上一顿乱翻,终于在万千线索中,找出了我觉得最有价值的一个。

这个问题,就和文章开头的读者问我的可以说一模一样了:

https://stackoverflow.com/questions/28975415/why-jdk-code-style-uses-a-variable-assignment-and-read-on-the-same-line-eg-i


image.png

这个哥们说:在 jdk 源码中,更具体地说,是在集合框架中,有一个编码的小癖好,就是在表达式中读取变量之前,先将其赋值到一个局部变量中。这只是一个简单的小癖好吗,还是里面藏着一下我没有注意到的更重要的东西?

随后,还有人帮他补充了几句:

image.png

Doug Lea 是集合框架和并发包的主要作者之一,他编码的时候倾向于进行一些优化。但是这些优化这可能会违反直觉,让普通人感到困惑。

毕竟人家是在大气层。

接着他给出了一段代码,里面有三个方法,来验证了不同的写法生成的不同的字节码:

image.png

三个方法分别如下:

image.png

对应的字节码我就不贴了,直接说结论:

The testSeparate method uses 41 instructions

The testInlined method indeed is a tad smaller, with 39 instructions

Finally, the testRepeated method uses a whopping 63 instructions

同样的功能,但是最后一种直接使用成员变量的写法生成的字节码是最多的。

所以他给出了和我前面一样的结论:

image.png

这种写法确实可以节省几个字节的字节码,这可能就是使用这种方式的原因。

但是...

主要啊,他要开始 but 了:

image.png

但是,在不论是哪个方法,在被 JIT 优化之后,产生的机器代码将与原始字节码“无关”。

可以非常确定的是:三个版本的代码最终都会编译成相同的机器码(汇编)。

因此,他的建议是:不要使用这种风格,只需编写易于阅读和维护的“愚蠢”代码。你会知道什么时候轮到你使用这些“优化”。

可以看到他在“write dumb code”上附了一个超链接,我挺建议你去读一读的:

https://www.oracle.com/technical-resources/articles/javase/devinsight-1.html

在这里面,你可以看到《Java Concurrency in Practice》的作者 Brian Goetz:

image.png

他对于“dumb code”这个东西的解读:


image.png

说:通常,在 Java 应用程序中编写快速代码的方法是编写“dumb code”——简单、干净,并遵循最明显的面向对象原则的代码。

很明显,tab = table 这种写法,并不是 “dumb code”。

image.png

好了,说回这个问题。这个老哥接着做了进一步的测试,测试结果是这样的:


image.png

他对比了 testSeparate 和 TestInLine 方法经过 JIT 优化之后的汇编,这两个方法的汇编是相同的。

但是,你要搞清楚的是这个小哥在这里说的是 testSeparate 和 testInLine 方法,这两个方法都是采用了局部变量的方式:

image.png

只是 testSeparate 的可读性比 testInLine 高了很多。

而 testInLine 的写法,就是 HashMap 的写法。

所以,他才说:我们程序员可以只专注于编写可读性更强的代码,而不是搞这些“骚”操作。JIT 会帮我们做好这些东西。

从 testInLine 的方法命名上来看,也可以猜到,这就是个内联优化。

它提供了一种(非常有限,但有时很方便)“线程安全”的形式:它确保数组的长度(如 HashMap 的 getNode 方法中的 tab 数组)在方法执行时不会改变。

他为什么没有提到我们更关心的 testRepeated 方法呢?

他也在回答里面提到这一点:

image.png

他说:这都不用看,这三个方法最终生成的汇编肯定是一模一样的。

但是现在他说的是:

it can not result in the same machine code

它不能产生相同的汇编


image.png

最后,这个老哥还补充了这个写法除了字节码层面优化之外的另一个好处:

一旦在这里对 n 进行了赋值,在 getNode 这个方法中 n 是不会变的。如果直接使用数组的长度,假设其他方法也同时操作了 HashMap,在 getNode 方法中是有可能感知到这个变化的。

这个小知识点我相信大家都知道,很直观,不多说了。

但是,看到这里,我们好像还是没找到问题的答案。

那就接着往下挖吧。

目录
相关文章
|
9月前
|
程序员 API
第6期 一文读懂版本号
第6期 一文读懂版本号
63 0
|
9月前
|
存储 编译器 测试技术
【3w字吐血总结 | 新手必看】全网最详细Go笔记
【3w字吐血总结 | 新手必看】全网最详细Go笔记
160 0
|
9月前
|
算法
刷题专栏(十八):第一个错误的版本
刷题专栏(十八):第一个错误的版本
59 0
|
缓存 自然语言处理 小程序
这个迭代写了个小程序,顺便整理了一份笔记 📒 (4000字)
这个迭代写了个小程序,顺便整理了一份笔记 📒 (4000字)
222 0
|
Java
【‘’注释‘’】哇哦,这是心动的感觉
【‘’注释‘’】哇哦,这是心动的感觉
92 0
【‘’注释‘’】哇哦,这是心动的感觉
|
缓存 Java 编译器
从源码里的一个注释,我追溯到了12年前,有点意思。 (下)
从源码里的一个注释,我追溯到了12年前,有点意思。 (下)
163 0
从源码里的一个注释,我追溯到了12年前,有点意思。 (下)
|
存储 缓存 Java
从源码里的一个注释,我追溯到了12年前,有点意思。 (上)
从源码里的一个注释,我追溯到了12年前,有点意思。 (上)
116 0
从源码里的一个注释,我追溯到了12年前,有点意思。 (上)
|
程序员
44 个神仙注释,太有才了……
有时候,我们会写一些非常有创意的注释,而有些注释确实让人不得不佩服 程序员的想象力。看到下面这些注释,相信每个人都会捧腹大笑。
113 0
44 个神仙注释,太有才了……
|
Java
Java源码阅读(不断补充)
Java源码阅读(不断补充)
129 0