从源码里的一个注释,我追溯到了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 方法中是有可能感知到这个变化的。

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

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

那就接着往下挖吧。

目录
相关文章
|
1天前
|
安全 Java 编译器
一个 Bug JDK 居然改了十年?
你敢相信么一个简单的Bug,JDK 居然花了十年时间才修改完成。赶快来看看到底是个什么样的 Bug?
9 1
一个 Bug JDK 居然改了十年?
|
7月前
|
XML 程序员 C#
C注释的高级使用技巧,让你的代码无敌了!
C注释的高级使用技巧,让你的代码无敌了!
35 0
|
7月前
|
Java
注释之背后:代码的解释者与保护者
注释之背后:代码的解释者与保护者
39 0
自 创 日 历 (在代码里有注释讲细节)
做日历主要是要确定好第一天是星期几,然后算间隔多少天,算出具体这一天是星期几,然后把我们想打印的打印出来,把每个月的第一天定位到该在的地方。
78 0
|
Java
【‘’注释‘’】哇哦,这是心动的感觉
【‘’注释‘’】哇哦,这是心动的感觉
83 0
【‘’注释‘’】哇哦,这是心动的感觉
|
Shell
Ansible概述和模块解释(你刚走过了今天,而扑面而来的却是昨天)(二)
Ansible概述和模块解释(你刚走过了今天,而扑面而来的却是昨天)(二)
226 0
Ansible概述和模块解释(你刚走过了今天,而扑面而来的却是昨天)(二)
|
Shell
Ansible概述和模块解释(你刚走过了今天,而扑面而来的却是昨天)(三)
Ansible概述和模块解释(你刚走过了今天,而扑面而来的却是昨天)(三)
254 0
Ansible概述和模块解释(你刚走过了今天,而扑面而来的却是昨天)(三)
|
运维 Devops Linux
Ansible概述和模块解释(你刚走过了今天,而扑面而来的却是昨天)(一)
Ansible概述和模块解释(你刚走过了今天,而扑面而来的却是昨天)(一)
207 0
Ansible概述和模块解释(你刚走过了今天,而扑面而来的却是昨天)(一)
|
程序员
代码界10个最“牛叉”的代码注释
要说到代码注释这个东西吧,其实很神奇,因为不管写不写注释,其实对于代码的运行没有任何的影响,注释的长短也没关系,因为编译器会对于所有的代码注释都是过滤掉的,其实注释非常重要,对后期的代码维护和重构至关重要,但是其实很多程序员童鞋在写代码时往往并不注意注释,所以导致自己回头看自己的代码时也都忘了写的是什么,本文给出了 StackOverflow 网友针对“你看到过的最好的代码注释是什么样的?”这个问题给出的回答的前10条。
6283 0
|
缓存 Java 编译器
从源码里的一个注释,我追溯到了12年前,有点意思。 (下)
从源码里的一个注释,我追溯到了12年前,有点意思。 (下)
151 0
从源码里的一个注释,我追溯到了12年前,有点意思。 (下)