使用 unsafe_使用Unsafe真的是关于速度或功能吗?

使用 unsafe

总览

大约6年前,我开始使用一个类,直到那时,它只是一个好奇心sun.misc.Unsafe 。 我曾使用它进行反序列化和重新抛出Exception,但没有使用它的全部功能或公开谈论它。

我看到的第一个严重使用Unsafe的开源库是Disruptor。 这使我感到鼓舞,它可以在稳定的库中使用。 大约一年后,我发布了我的第一个开源库SharedHashMap(后来的Chronicle Map)和Chronicle(后来的Chronicle Queue)。 它使用Unsafe来访问Java 6中的堆外内存。这对堆外内存的性能产生了真正的影响,但更重要的是,我可以使用共享内存来做。 即跨JVM共享的数据结构。

但是今天有什么不同呢? 使用不安全总是更快吗?

我们正在寻找引人注目的性能差异。 如果差异不那么明显,则使用尽可能简单的代码更有意义。 即使用自然的Java。

测试

在这些测试中,我对源自堆外内存的数据进行了简单的累积。 这是一个简单的测试,它对解析数据(或散列数据)建模,这些数据是从堆(例如,TCP连接或文件系统)中生成的。 数据大小为128个字节。 下面的结果可能受到数据大小的影响,但这被认为具有代表性。

我查看的是不同的访问大小,一次访问一个字节,一个int或一个long。 我还研究了使用ByteBuffer还是在堆上复制数据并使用自然Java(我假设这是大多数程序执行此操作的方式)。

我还比较了使用Java 6更新45,Java 7更新79,Java 8更新51的情况,以了解不同发行版之间使用不同方法的变化。

逐字节处理

处理器设计中真正改善的一点是它可以复制大型数据块的速度。 这意味着复制大量数据,以便可以更有效地处理它是有意义的。 也就是说,冗余副本可能足够便宜,从而可以带来更快的解决方案。

逐字节处理就是这种情况。 在此示例中,“在堆上”包括在处理之前在堆上复制数据的副本。 这些数字是在i7-3790X上每微秒的操作数。

Java 6 Java 7 Java 8
字节缓冲区 15.8 16.9 16.4
不安全 17.2 17.5 16.9
在堆上 20.9 22.0 21.9


这样做的重要意义在于,“堆上”不仅使用自然Java,而且在所有三个版本的Java中都是最快的。最可能的解释是,JIT具有在堆上情况下可以进行的优化。如果您直接或间接使用不安全,则不会这样做。

由int处理的int。

解析冗长的有线协议的更快方法是一次读取一个int。 例如,您可以通过一次读取一个int而不是单独查看每个字节来编写已知格式的XML解析器。 这样可以将解析速度提高2到3倍。 这种方法最适合已知结构的内容。

Java 6 Java 7 Java 8
字节缓冲区 12.6 36.2 35.1
不安全 44.5 52.7 54.7
在堆上 46.0 49.5 56.2


同样,这是i7-3790X上每微秒的操作。 有趣的是,在复制后使用自然Java大约与使用Unsafe一样快。 对于此用例,也没有令人信服的理由使用Unsafe。

长期加工

尽管您可以编写一个解析器一次读取一个64位长的值,但我发现这比使用32位int值解析要困难得多。 我也没有发现结果要快得多。 但是,只要在设计散列算法时考虑到这一点,就可以从读取长值中受益于对数据结构进行散列。

Java 6 Java 7 Java 8
字节缓冲区 12.1 56.7 53.3
不安全 66.7 83.0 94.9
在堆上 60.9 61.2 70.0


有趣的是,使用ByteBuffer的速度提高了多少。 最可能的解释是在ByteBuffer中增加了将little-endian交换为默认big-endian的优化。 x86有一条交换字节的指令,但是我怀疑Java 6没有使用它,而是使用了更昂贵的移位操作。 为了能够确认这一点,将需要进行更多的测试并检查生成的汇编代码。

在这种情况下,使用Unsafe的速度总是更快,无论您是否认为这种改进值得直接使用Unsafe带来的风险是另一回事。

补充笔记

这些测试假设使用字节,整数或长整数的统一数据类型。

在大多数实际情况下,这些数据类型是结合在一起的,这就是在堆上挣扎的地方。 例如,如果您需要解析字节,短裤,整数,长整数,浮点数,双精度数的任意组合。 ByteBuffer是执行此操作的好方法,但是在每种情况下,它都是最慢的选项。 只有不安全才能让您灵活地混合和匹配类型,而不会产生开销。

对于这些混合类型,很难对堆进行公平的测试,因为自然Java不直接支持这些操作。

结论

即使性能是您最关心的问题,在某些情况下,自然Java的性能还是更好,或与使用Unsafe一样快。 它经常执行ByteBuffer,因为JIT可以更好地优化开销,例如对自然Java代码进行边界检查。

自然的Java代码依赖于我们可以将数据建模为byte [],int []或long []的事实。 没有数组或原始类型混合的选项。

自然Java挣扎的地方在于对这两种方式的支持范围

  • 不同原始类型的任意组合,例如字节,整数,长整数,双精度数。
  • 共享/本机内存上的线程安全操作。

不幸的是,由于缺乏自然Java支持,因此很难创建公平的基准来比较性能。

总而言之,如果您可以用自然Java实现算法,那么它可能是最快也是最简单的。 如果您需要混合使用数据类型或线程安全堆进行语法分析,那么仍然没有很好的方法来使用自然Java。

注意:这是Java 9中的VarHandles应该能够提供帮助的区域,因此请观看此空间以获取VarHandles的更新。

翻译自: https://www.javacodegeeks.com/2015/08/is-using-unsafe-really-about-speed-or-functionality.html

使用 unsafe

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值