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};

能用 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);
}

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

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

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

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

仔细观察线性方程就会发现,对于每一个纬度,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);

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

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

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);
}

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

FloatVector vy = va.mul(vx);

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

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

在这里插入图片描述

三、飙升的性能

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

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

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

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

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

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

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

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

四、总结

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

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

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

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

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

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
/* * 基于数组的向量实现 */ package dsa; public class Vector_Array implements Vector { private final int N = 1024;//数组的容量 private int n = 0;//向量的实际规模 private Object[] A;//对象数组 //构造函数 public Vector_Array() { A = new Object[N]; n = 0; } //返回向量中元素数目 public int getSize() { return n; } //判断向量是否为空 public boolean isEmpty() { return (0 == n) ? true : false; } //取秩为r的元素 public Object getAtRank(int r)//O(1) throws ExceptionBoundaryViolation { if (0 > r || r >= n) throw new ExceptionBoundaryViolation("意外:秩越界"); return A[r]; } //将秩为r的元素替换为obj public Object replaceAtRank(int r, Object obj) throws ExceptionBoundaryViolation { if (0 > r || r >= n) throw new ExceptionBoundaryViolation("意外:秩越界"); Object bak = A[r]; A[r] = obj; return bak; } //插入obj,作为秩为r的元素;返回该元素 public Object insertAtRank(int r, Object obj) throws ExceptionBoundaryViolation { if (0 > r || r > n) throw new ExceptionBoundaryViolation("意外:秩越界"); if (n >= N) throw new ExceptionBoundaryViolation("意外:数组溢出"); for (int i=n; i>r; i--) A[i] = A[i-1];//后续元素顺次后移 A[r] = obj;//插入 n++;//更新当前规模 return obj; } //删除秩为r的元素 public Object removeAtRank(int r) throws ExceptionBoundaryViolation { if (0 > r || r >= n) throw new ExceptionBoundaryViolation("意外:秩越界"); Object bak = A[r]; for (int i=r; i<n; i++) A[i] = A[i+1];//后续元素顺次前移 n--;//更新当前规模 return bak; } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿昌喜欢吃黄桃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值