Java 内幕新闻第二期深度解读

简介: Java 内幕新闻第二期深度解读

⎯⎯⎯⎯⎯⎯ Chapters ⎯⎯⎯⎯⎯⎯

  • 0:00 - Intro
  • 0:33 - Vector API
  • 0:56 - Vector API - SIMD and Vector Instructions
  • 2:22 - Vector API - Current State
  • 3:10 - Vector API - More Inside Java podcast Ep. 7
  • 3:59 - Records Serialization
  • 5:22 - JDK 17 - Enhanced Pseudo-Random Number Generators
  • 6:06 - Outro

这一节的内容不是很多,但是都比较有意思。


Vector API


相关 JEP:

其中最主要的应用就是使用了 CPU 的 SIMD(单指令多数据)处理,它提供了通过程序的多通道数据流,可能有 4 条通道或 8 条通道或任意数量的单个数据元素流经的通道。并且 CPU 一次在所有通道上并行组织操作,这可以极大增加 CPU 吞吐量。通过 Vector API,Java 团队正在努力让 Java 程序员使用 Java 代码直接访问它;过去,他们必须在汇编代码级别对向量数学进行编程,或者使用 C/C++ 与 Intrinsic 一起使用,然后通过 JNI 提供给 Java。

一个主要的优化点就是循环,过去的循环(标量循环),一次在一个元素上执行,那很慢。现在,您可以使用 Vector API 将标量算法转换为速度更快的数据并行算法。一个使用 Vector 的例子:

//测试指标为吞吐量
@BenchmarkMode(Mode.Throughput)
//需要预热,排除 jit 即时编译以及 JVM 采集各种指标带来的影响,由于我们单次循环很多次,所以预热一次就行
@Warmup(iterations = 1)
//单线程即可
@Fork(1)
//测试次数,我们测试10次
@Measurement(iterations = 10)
//定义了一个类实例的生命周期,所有测试线程共享一个实例
@State(value = Scope.Benchmark)
public class VectorTest {
  private static final VectorSpecies<Float> SPECIES =
      FloatVector.SPECIES_256;
  final int size = 1000;
  final float[] a = new float[size];
  final float[] b = new float[size];
  final float[] c = new float[size];
  public VectorTest() {
    for (int i = 0; i < size; i++) {
      a[i] = ThreadLocalRandom.current().nextFloat(0.0001f, 100.0f);
      b[i] = ThreadLocalRandom.current().nextFloat(0.0001f, 100.0f);
    }
  }
  @Benchmark
  public void testScalar(Blackhole blackhole) throws Exception {
    for (int i = 0; i < a.length; i++) {
      c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
    }
  }
  @Benchmark
  public void testVector(Blackhole blackhole) {
    int i = 0;
    //高于数组长度的 SPECIES 一次处理数据长度的倍数
    int upperBound = SPECIES.loopBound(a.length);
    //每次循环处理 SPECIES.length() 这么多的数据
    for (; i < upperBound; i += SPECIES.length()) {
      // FloatVector va, vb, vc;
      var va = FloatVector.fromArray(SPECIES, a, i);
      var vb = FloatVector.fromArray(SPECIES, b, i);
      var vc = va.mul(va)
          .add(vb.mul(vb))
          .neg();
      vc.intoArray(c, i);
    }
    for (; i < a.length; i++) {
      c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
    }
  }
  public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder().include(VectorTest.class.getSimpleName()).build();
    new Runner(opt).run();
  }
}

注意使用处于孵化的 Java 特性需要加上额外的启动参数将模块暴露,这里是--add-modules jdk.incubator.vector,需要在 javac 编译和 java 运行都加上这些参数,使用 IDEA 即:


image.png


image.png


测试结果:

Benchmark               Mode  Cnt         Score         Error  Units
VectorTest.testScalar  thrpt   10   7380697.998 ± 1018277.914  ops/s
VectorTest.testVector  thrpt   10  37151609.182 ± 1011336.900  ops/s

其他使用,请参考:fizzbuzz-simd-style,这是一篇比较有意思的文章(虽然这个性能优化感觉不只由于 SIMD,还有算法优化的功劳,哈哈)

关于一些更加详细的使用,以及设计思路,可以参考这个音频:https://www.youtube.com/watch?v=VYo3p4R66

N8&t=427s

Records Serialization


关于 Java Record 的序列化,我也写过一篇文章进行分析,参考:Java Record 的一些思考 - 序列化相关

其中,最重要的是一些主流的序列化框架的兼容

由于 Record 限制了序列化与反序列化的唯一方式,所以其实兼容起来很简单,比起 Java Class 改个结构,加个特性导致的序列化框架更改来说还要简单。




这三个框架中实现对于 Record 的兼容思路都很类似,也比较简单,即:

  1. 实现一个针对 Record 的专用的 Serializer 以及Deserializer。
  2. 通过反射(Java Reflection)或者句柄(Java MethodHandle)验证当前版本的 Java 是否支持 Record,以及获取 Record 的规范构造函数(canonical constructor)以及各种 field 的 getter 进行反序列化和序列化。


JDK 17 - Enhanced Pseudo-Random Number Generators


Java 17 针对随机数生成器做了统一接口封装,并且内置了 Xoshiro 算法以及自己研发的 LXM 算法,可以参考我的这个系列文章:

这里截取一部分分析:

根据之前的分析,应该还是 SplittableRandom 在单线程环境下最快,多线程环境下使用 ThreadLocalRandom 最快。新增的随机算法实现类,Period 约大需要的计算越多, LXM 的实现需要更多计算,加入这些算法是为了适应更多的随机应用,而不是为了更快。不过为了满足大家的好奇心,还是写了如下的代码进行测试,从下面的代码也可以看出,新的 RandomGenerator API 使用更加简便:
package prng;
import java.util.random.RandomGenerator;
import java.util.random.RandomGeneratorFactory;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
//测试指标为吞吐量
@BenchmarkMode(Mode.Throughput)
//需要预热,排除 jit 即时编译以及 JVM 采集各种指标带来的影响,由于我们单次循环很多次,所以预热一次就行
@Warmup(iterations = 1)
//线程个数
@Threads(10)
@Fork(1)
//测试次数,我们测试50次
@Measurement(iterations = 50)
//定义了一个类实例的生命周期,所有测试线程共享一个实例
@State(value = Scope.Benchmark)
public class TestRandomGenerator {
  @Param({
      "Random", "SecureRandom", "SplittableRandom", "Xoroshiro128PlusPlus", "Xoshiro256PlusPlus", "L64X256MixRandom",
      "L64X128StarStarRandom", "L64X128MixRandom", "L64X1024MixRandom", "L32X64MixRandom", "L128X256MixRandom",
      "L128X128MixRandom", "L128X1024MixRandom"
  })
  private String name;
  ThreadLocal<RandomGenerator> randomGenerator;
  @Setup
  public void setup() {
    final String finalName = this.name;
    randomGenerator = ThreadLocal.withInitial(() -> RandomGeneratorFactory.of(finalName).create());
  }
  @Benchmark
  public void testRandomInt(Blackhole blackhole) throws Exception {
    blackhole.consume(randomGenerator.get().nextInt());
  }
  @Benchmark
  public void testRandomIntWithBound(Blackhole blackhole) throws Exception {
    //注意不取 2^n 这种数字,因为这种数字一般不会作为实际应用的范围,但是底层针对这种数字有优化
    blackhole.consume(randomGenerator.get().nextInt(1, 100));
  }
  public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder().include(TestRandomGenerator.class.getSimpleName()).build();
    new Runner(opt).run();
  }
}

测试结果:

Benchmark                                                  (name)   Mode  Cnt          Score           Error  Units
TestRandomGenerator.testRandomInt                          Random  thrpt   50  276250026.985 ± 240164319.588  ops/s
TestRandomGenerator.testRandomInt                    SecureRandom  thrpt   50    2362066.269 ±   1277699.965  ops/s
TestRandomGenerator.testRandomInt                SplittableRandom  thrpt   50  365417656.247 ± 377568150.497  ops/s
TestRandomGenerator.testRandomInt            Xoroshiro128PlusPlus  thrpt   50  341640250.941 ± 287261684.079  ops/s
TestRandomGenerator.testRandomInt              Xoshiro256PlusPlus  thrpt   50  343279172.542 ± 247888916.092  ops/s
TestRandomGenerator.testRandomInt                L64X256MixRandom  thrpt   50  317749688.838 ± 245196331.079  ops/s
TestRandomGenerator.testRandomInt           L64X128StarStarRandom  thrpt   50  294727346.284 ± 283056025.396  ops/s
TestRandomGenerator.testRandomInt                L64X128MixRandom  thrpt   50  314790625.909 ± 257860657.824  ops/s
TestRandomGenerator.testRandomInt               L64X1024MixRandom  thrpt   50  315040504.948 ± 101354716.147  ops/s
TestRandomGenerator.testRandomInt                 L32X64MixRandom  thrpt   50  311507435.009 ± 315893651.601  ops/s
TestRandomGenerator.testRandomInt               L128X256MixRandom  thrpt   50  187922591.311 ± 137220695.866  ops/s
TestRandomGenerator.testRandomInt               L128X128MixRandom  thrpt   50  218433110.870 ± 164229361.010  ops/s
TestRandomGenerator.testRandomInt              L128X1024MixRandom  thrpt   50  220855813.894 ±  47531327.692  ops/s
TestRandomGenerator.testRandomIntWithBound                 Random  thrpt   50  248088572.243 ± 206899706.862  ops/s
TestRandomGenerator.testRandomIntWithBound           SecureRandom  thrpt   50    1926592.946 ±   2060477.065  ops/s
TestRandomGenerator.testRandomIntWithBound       SplittableRandom  thrpt   50  334863388.450 ±  92778213.010  ops/s
TestRandomGenerator.testRandomIntWithBound   Xoroshiro128PlusPlus  thrpt   50  252787781.866 ± 200544008.824  ops/s
TestRandomGenerator.testRandomIntWithBound     Xoshiro256PlusPlus  thrpt   50  247673155.126 ± 164068511.968  ops/s
TestRandomGenerator.testRandomIntWithBound       L64X256MixRandom  thrpt   50  273735605.410 ±  87195037.181  ops/s
TestRandomGenerator.testRandomIntWithBound  L64X128StarStarRandom  thrpt   50  291151383.164 ± 192343348.429  ops/s
TestRandomGenerator.testRandomIntWithBound       L64X128MixRandom  thrpt   50  217051928.549 ± 177462405.951  ops/s
TestRandomGenerator.testRandomIntWithBound      L64X1024MixRandom  thrpt   50  222495366.798 ± 180718625.063  ops/s
TestRandomGenerator.testRandomIntWithBound        L32X64MixRandom  thrpt   50  305716905.710 ±  51030948.739  ops/s
TestRandomGenerator.testRandomIntWithBound      L128X256MixRandom  thrpt   50  174719656.589 ± 148285151.049  ops/s
TestRandomGenerator.testRandomIntWithBound      L128X128MixRandom  thrpt   50  176431895.622 ± 143002504.266  ops/s
TestRandomGenerator.testRandomIntWithBound     L128X1024MixRandom  thrpt   50  198282642.786 ±  24204852.619  ops/s

在之前的结果验证中,我们已经知道了 SplittableRandom 的在单线程中的性能最好,多线程环境下表现最好的是算法与它类似但是做了多线程优化的 ThreadLocalRandom.

如何选择随机算法

原则是,看你的业务场景,所有的随机组合到底有多少个,在什么范围内。然后找大于这个范围的 Period 中,性能最好的算法。例如,业务场景是一副扑克除了大小王 52 张牌,通过随机数决定发牌顺序:

  • 第一张牌:randomGenerator.nextInt(0, 52),从剩余的 52 张牌选
  • 第二张牌:randomGenerator.nextInt(0, 51),从剩余的 51 张牌选
  • 以此类推

那么一共有 52! 这么多结果,范围在 2^225 ~ 2^226 之间。如果我们使用的随机数生成器的 Period 小于这个结果集,那么某些牌的顺序,我们可能永远生成不了。所以,我们需要选择一个 Period > 54! 的随机数生成器。


相关文章
|
8月前
|
Java 关系型数据库 MySQL
基于Java的体育网站的设计与实现(论文+源码)_kaic
基于Java的体育网站的设计与实现(论文+源码)_kaic
基于Java的体育网站的设计与实现(论文+源码)_kaic
|
6天前
|
算法 搜索推荐 Java
【潜意识Java】深度解析黑马项目《苍穹外卖》与蓝桥杯算法的结合问题
本文探讨了如何将算法学习与实际项目相结合,以提升编程竞赛中的解题能力。通过《苍穹外卖》项目,介绍了订单配送路径规划(基于动态规划解决旅行商问题)和商品推荐系统(基于贪心算法)。这些实例不仅展示了算法在实际业务中的应用,还帮助读者更好地准备蓝桥杯等编程竞赛。结合具体代码实现和解析,文章详细说明了如何运用算法优化项目功能,提高解决问题的能力。
41 6
|
6天前
|
Java 数据库连接 数据库
【潜意识Java】深度分析黑马项目《苍穹外卖》在Java学习中的重要性
《苍穹外卖》项目对Java学习至关重要。它涵盖了用户管理、商品查询、订单处理等模块,涉及Spring Boot、MyBatis、Redis等技术栈。
30 4
GitHub最新发布Java面试突击手册+P5-P8学习图谱,一夜直接竟爆火
什么是金九银十? “金九银十指的是每年的九十月份都是人才招聘的高峰期,因为跟春节和春运紧接,到人才市场,人都是满的,所以称为金九;伴随的十月则称为银十。”
|
8月前
|
消息中间件 Java 关系型数据库
又一里程碑!阿里首推Java技术成长笔记,业内评级“钻石级”
根据数据表明,阿里巴巴已经连续3年获评最受欢迎的中国互联网公司,实际上阿里巴巴无论在科技创新力还是社会创造价值这几个方面,都是具有一定代表里的。在行业内,很多互联网企业也将阿里作为自己的标杆,越来越多的“打工人”也希望能够进到阿里工作。
|
Java 关系型数据库 MySQL
破防了!阿里P8裸辞真实心路历程,他底气来源于Java面试核心笔记
我为什么从阿里离职? 终于从阿里裸辞了,阿里带来的强大的压力让人有一些窒息。坦白来讲,最初进阿里应该是抱着满腔的期待和向往的,甚至做好了三年规划,五年规划。但这一年多经历了比较多的事情,我一贯的想法是,事情发生了,都是有ab面的,有好有坏。但的确有那么一些时刻,自己会感觉糟透了,并不是几句鸡汤抑或是打几下鸡血能够拯救的,糟透了。然后跟一些同样境遇的人进行过几次深入的沟通,大家最后都觉得出去看看是一个不坏的决定。 于是我毅然而然地选择了从阿里离开了,并不是我有多大的经济实力,是这不是我想要的生活,确实阿里P8的岗位给我的职业生涯镀了一层金,但这并不是阻止自我进步的理由和借口,我们应该借风而起
200 0
|
Java 程序员
【软件创新实验室2021年寒假集训】Java技术培训——Java基础(二)
【软件创新实验室2021年寒假集训】Java技术培训——Java基础(二)
【软件创新实验室2021年寒假集训】Java技术培训——Java基础(二)
|
IDE Oracle Java
【软件创新实验室2021年寒假集训】Java技术培训——Java前置知识学习
【软件创新实验室2021年寒假集训】Java技术培训——Java前置知识学习
【软件创新实验室2021年寒假集训】Java技术培训——Java前置知识学习
|
算法 Java
java学习第七天笔记-方法143-综合练习-双色球算法2
java学习第七天笔记-方法143-综合练习-双色球算法2
82 0
java学习第七天笔记-方法143-综合练习-双色球算法2
|
算法 Java
java学习第七天笔记-方法144-综合练习-双色球算法3
java学习第七天笔记-方法144-综合练习-双色球算法3
85 0
java学习第七天笔记-方法144-综合练习-双色球算法3