使用 JMH 找到最快的编码/解码 UTF-8 的方法

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

我以前使用过 JMH,但这是我第一次使用 JMH 来解决生产问题。我对如何优化所涉及的代码有一些想法,但尝试与 JMH 的不同组合会带来显着的改进。

在此测试中,我将字符串编码为 UTF8 以引导内存,以便将其写入 TCP 套接字。我还需要通过 NIO SocketChannel.read 将数据写入直接内存并将其编码为 StringBuilder (可以重复使用)

测试

这些测试包括使用反射获取 String 和 StringBuilder 的底层数据结构以及使用 Unsafe 访问本机内存的组合。

令我惊讶的是,通过反射访问 String 似乎并不快,甚至可能更慢。对于较短的字符串,情况更糟,这表明使用反射的开销大于任何好处。

请参阅 encode_unsafeLoopCharA t 和 encode_unsafeLoopCharAt

另一个惊喜是通过反射访问 StringBuilder 确实有所不同。

请参见 decode_usingSimpleLoop decode_usingCharArray 访问底层 char[] 的速度提高了 4 倍以上。注意:这是一个明显的优化问题,未来的 Java 版本可能不会有这个问题。

所有结果


 Benchmark                                    Mode  Cnt         Score        Error  Units
DecodeMain.decode_fromUTF8                  thrpt   20   5395187.171 �  17525.486  ops/s
DecodeMain.decode_usingCharArray            thrpt   20   7967263.552 � 259148.327  ops/s
DecodeMain.decode_usingCharArrayAndAddress  thrpt   20  11644515.566 �  52786.179  ops/s
DecodeMain.decode_usingSimpleLoop           thrpt   20   1884355.264 �   3442.892  ops/s
EncodeMain.encode_simpleToUTF8              thrpt   20   5050422.611 �  31681.322  ops/s
EncodeMain.encode_unsafeLoopCharArray       thrpt   20  16837387.866 � 814047.308  ops/s
EncodeMain.encode_unsafeLoopCharAt          thrpt   20  18225151.521 � 132811.688  ops/s
EncodeMain.encode_unsafeLoopCharAtUnrolled  thrpt   20  13848365.955 � 102407.681  ops/s
EncodeMain.encode_usingSimpleLoop           thrpt   20   8868356.295 � 368546.131  ops/s
EncodeMain.encode_usingSimpleLoopUnrolled   thrpt   20   7077634.663 �  30359.636  ops/s

结论

将来此功能可能会内置到 JVM 中,但是可能有一些功能未内置到 JVM 中,这会导致性能问题,因此需要一个替代方案。