关于java:Java-内幕新闻第二期深度解读

30次阅读

共计 8368 个字符,预计需要花费 21 分钟才能阅读完成。

这是由 Java 官网公布,Oracle JDK 研发 Nipafx 制作的节目,蕴含 JDK 近期的研发停顿和新个性瞻望和应用,这里加上集体译制的字幕搬运而来。我把 Nipafx 的扩大材料具体研读并提取精髓做了集体具体解读:视频地址(熟肉)

⎯⎯⎯⎯⎯⎯ 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:

  • JEP 338: Vector API (Incubator)
  • JEP 414: Vector API (Second Incubator):Java 17 中的
  • JEP 417: Vector API (Third Incubator):Java 18 中的

其中最次要的利用就是应用了 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 即:

测试后果:

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…

Records Serialization

对于 Java Record 的序列化,我也写过一篇文章进行剖析,参考:[Java Record 的一些思考 – 序列化相干]()

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

因为 Record 限度了序列化与反序列化的惟一形式,所以其实兼容起来很简略,比起 Java Class 改个构造,加个个性导致的序列化框架更改来说还要简略。

  • Jackson:

    • Issue: Support for record types in JDK 14
    • Pull Request: Support for record types in JDK 14
    • 对应版本:jackson-databind-2.12.0
  • Kryo

    • Issue: Java 14 records : how to deal with them?
    • Pull Request: Add support for Records in JDK 14
    • 对应版本:kryo-5.1.0
  • XStream

    • Issue: Support for record types in JDK 14
    • Pull Request: Add support for Record types in JDK 14
    • 对应版本:1.5.x,还未公布

这三个框架中实现对于 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 算法,能够参考我的这个系列文章:

  • 硬核 – Java 随机数相干 API 的演进与思考(上)
  • 硬核 – Java 随机数相干 API 的演进与思考(下)

这里截取一部分剖析:

依据之前的剖析,应该还是 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! 的随机数生成器。

微信搜寻“我的编程喵”关注公众号,每日一刷,轻松晋升技术,斩获各种 offer

正文完
 0