• Day719. 矢量运算 -Java8后最重要新特性


    矢量运算

    Hi,阿昌来也,今天学习记录的是关于此时此刻还在预览阶段的矢量运算

    Java 的矢量运算,我写这篇文章的时候还在孵化期,还没有发布预览版

    这个技术代表了 Java 语言发展的一个重要方向,在未来一定会有着重要的影响。

    早一点了解这样的技术,除了扩展视野之外,还能够帮助制定未来几年要学习或者要使用的技术路线

    然后,再看看矢量运算能够带来什么样的变化。

    一、阅读案例

    线性方程(或者说一次方程)一定不陌生。

    一般情况下,我们可以把线性方程表述成下面的形式。

    在这里插入图片描述

    其中 a0​,a1​,an−1​ 表示的是常数,x0​,x1​,xn−1​ 表示的是变量,而 y 就表示 ai​ 和 xi​ 的组合结果。n 表示未知变量的数目,通常,把它称为方程的维度。

    如果给定方程式右边的常数和变量,我们就能计算出方程式左边的 y 数值了。

    那么,该怎么用代码表示这个方程式呢?

    可以把 a0​,a1​,an−1​ 表示的常数放到一个数组里,把 x0​,x1​,xn−1​ 表示的变量放到另外一个数组里。

    下面的代码里,变量 a 和 x 就可以用来表示一个有四个维度的一次方程组

    static final float[] a = new float[] {0.6F, 0.7F, 0.8F, 0.9F};
    static final float[] x = new float[] {1.0F, 2.0F, 3.0F, 4.0F};
    
    • 1
    • 2

    能用 Java 的变量来表示一次方程,能够计算线性方程的结果了。

    下面的代码,就是一个实现的办法。

    private static Returned<Float> sumInScalar(float[] a, float[] x) {
        if (a == null || x == null || a.length != x.length) {
            return new Returned.ErrorCode(-1);
        }
        float[] y = new float[a.length];
        for (int i = 0; i < a.length; i++) {
            y[i] = a[i] * x[i];
        }
        float r = 0F;
        for (int i = 0; i < y.length; i++) {
            r += y[i];
        }
        return new Returned.ReturnValue<>(r);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在上面的代码里,先计算 ai​ 和 xi​ 的乘积,然后再计算乘积结果的总和。

    其中的乘法运算,就是常说的标量运算

    为了方便讨论,把乘法运算的代码单独拿出来,粘贴在下面。

    float[] y = new float[a.length];
    for (int i = 0; i < a.length; i++) {
        y[i] = a[i] * x[i];
    }
    
    • 1
    • 2
    • 3
    • 4

    仔细观察线性方程就会发现,对于每一个纬度,ai​ 和 xi​ 是互不影响的, 当然它们的乘积也是互不影响的。

    既然每个维度的计算都互不影响,那么能不能并行计算呢?

    二、矢量运算

    Java 的矢量运算就是使用单个指令并行处理多个数据的一个尝试(单指令多数据,Single Instruction Multiple Data)。

    在现代的微处理器(CPU)中,一个控制器可以控制多个平行的处理单元;

    在现代的图形处理器(GPU)中呢,更是拥有强大的并发处理能力和可编程流水线。这些处理器层面的技术,为软件层面的单指令多数据处理提供了物理支持。

    Java 矢量运算的设计和实现,也是希望能够借助现代处理器的这种能力,提高运算的性能。

    为了使用单指令多数据的指令,需要把不同数据的运算独立出来,让并行运算成为可能。

    而数学里的矢量运算,恰好就能满足这样的要求。

    如果使用矢量,可以把线性方程表述成下面的形式(使用向量的数量积形式):

    在这里插入图片描述

    其中,a,x 和 y′ 是三个 n 维的矢量。

    在这里插入图片描述

    好了,现在看看 Java 是怎么表达矢量的了。

    下面代码里的变量 a,和前面阅读案例里 a 是一样的,它以数组的形式表示;

    变量 va,就是变量 a 的矢量表达形式。

    fromArray 这个方法,可以把一个数组变量,转换成一个矢量的变量。

    static final float[] a = new float[] {0.6F, 0.7F, 0.8F, 0.9F};
    static final FloatVector va =
            FloatVector.fromArray(FloatVector.SPECIES_128, a, 0);
            
    static final float[] x = new float[] {1.0F, 2.0F, 3.0F, 4.0F};
    static final FloatVector vx =
            FloatVector.fromArray(FloatVector.SPECIES_128, x, 0);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    有了表示矢量的办法,试着使用矢量运算的办法,来计算线性方程的结果了。

    下面的代码,就是一个简化了的实现。

    private static Returned<Float> sumInVector(FloatVector va, FloatVector vx) {
        if (va == null || vx == null || va.length() != vx.length()) {
            return new Returned.ErrorCode(-1);
        }
        
        // FloatVector vy = va.mul(vx);
        float[] y = va.mul(vx).toArray();
        
        float r = 0F;
        for (int i = 0; i < y.length; i++) {
            r += y[i];
        }
        return new Returned.ReturnValue<>(r);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    这个运算的关键部分是其中的矢量运算,也就是下面这行代码。

    FloatVector vy = va.mul(vx);
    
    • 1

    和上面的标量运算的办法相比,矢量运算的代码精简了很多。

    这是矢量运算的第一个优点。但优点还不止于此。

    在这里插入图片描述

    三、飙升的性能

    Java 矢量运算的设计,主要是为了性能

    那么,性能的提升能有多大呢?

    做了一个性能测试。虽然这个特性还处于孵化期,但是性能测试结果还是很令人振奋的。

    就上面这个简单的、四维的矢量来说,和在阅读案例里使用的标量运算相比,矢量运算的性能提高了足足有 10 倍。

    Benchmark                       Mode  Cnt           Score           Error  Units
    VectorBench.scalarComputation  thrpt   15   180635563.597 ±  30893274.582  ops/s
    VectorBench.vectorComputation  thrpt   15  1839556188.443 ± 153876900.442  ops/s
    
    • 1
    • 2
    • 3

    对于一个还处于孵化阶段的实现来说,这么大的性能提升是有点超出预料的。

    密码学机器学习领域,通常需要处理几百甚至几千维的数据。

    一般情况下,为了能够使用处理器的计算优势,经常需要特殊的设计以及内嵌于 JVM 的本地代码来获得硬件加速。

    这样的限制,让普通代码的计算很难获得硬件加速的好处。

    希望成熟后的 Java 矢量运算,能在这些领域有出色的表现,让普通的代码获得处理器的单指令多数据的强大运算能力。

    毕竟,只有单指令多数据的优势能够被普通的 Java 应用程序广泛使用,Java 才能在机器学习、科学计算这些领域获得计算优势。

    如果从机器学习在未来的重要性来说,Java 在科学计算领域的拓展来得也许正是时候。

    四、总结

    Java 的矢量运算这个尚处于孵化阶段的新特性,对 Java 的矢量运算这个新特性有了一个初始的印象。

    如果 Java 矢量运算成熟起来,许多领域都可以从这个新特性中受益,包括但是不限于机器学习、线性代数、密码学、金融和 JDK 本身的代码。这一次学习的主要目的,就是让你对矢量运算有一个基本的印象。

    这样的话,如果代码里有大量的数值计算,也许可以考虑在将来使用矢量运算获得硬件的并行计算能力,大幅度提高代码的性能

    由于矢量运算尚处于孵化阶段,目前还不需要学习它的 API,知道 Java 有这个发展方向,并且能够思考你的代码潜在的改进空间就足够了。

    知道了这个方向,等 Java 矢量运算正式发布的时候,你就可以尽早地改进你的代码,从而获得领先的优势了。

    如果面试中聊到了数值计算的性能,你应该知道有矢量运算这么一个潜在的方向,以及“单指令多数据”这么一个术语。

  • 相关阅读:
    如何设计大电流九线导电滑环
    全局Ceph节点宕机处理
    Linux基础命令[24]-su
    大模型系统和应用——高效训练&模型压缩
    【微服务~Sentinel】Sentinel之dashboard控制面板
    像素间的关系
    高项 整体管理论文
    12种算法优化CNN-BiGRU-Attention单变量输入单步预测,机器学习预测全家桶,持续更新,MATLAB代码...
    KEGG通路图绘制 | ggpathway包
    CANoe-激活总线状态、CAPL导航器不显示Test Case视图
  • 原文地址:https://blog.csdn.net/qq_43284469/article/details/126533134