Java:从Java 8开始受益于内联类属性

希望在几年内,Java将具有“内联类”功能,该功能可以解决Java当前状态下的许多挑战。 阅读本文并学习如何立即使用Java 8或更高版本,并且仍将受益于即将出现的内联对象数组的一些优点,例如; 没有间接指针,消除了对象标头开销,并改善了数据局部性。

在本文中,我们将学习如何编写一个名为
InlineArray支持将来的许多内联类功能。 我们还将看一下Speedment HyperStream,这是一个使用类似操作方法的现有Java工具。

背景

自1995年以来,当从完全合理的角度出发时,Java中的Objects数组由一个数组组成,该数组又包含对其他对象的大量引用,这些引用最终分散在堆中。

现在,这是在Java中将具有两个初始Point对象的数组布置在堆上的方式:

 Array  +======+  |Header|  +------+     Point 0  |ref 0 |---> +======+  +------+    |Header|      Point +------+    |Header|      Point 1  |ref 1 |---- +------+ ---> +======+  +------+    |x    |     |Header|  | null |    +------+     +------+ |    +------+     +------+  +------+    |y    |     |x    |  | null |    +------+     +------+ |    +------+     +------+  +------+                  |y    |  |...  |                  +------+  +------+ 

但是,随着时间的流逝,典型的CPU的执行流水线已经发生了巨大的变化,并且计算性能得到了惊人的提高。 另一方面,光速保持恒定,因此,不幸的是,从主存储器加载数据的等待时间保持在相同的数量级内。 计算和检索之间的平衡已偏向于计算。

这些天来访问主内存已成为我们要避免的事情,就像我们希望避免过去从旋转磁盘中加载数据一样。

显然,当前的Object数组布局具有几个缺点,例如:

  • 双内存访问(由于数组中的间接引用指针)
  • 数据局部性降低(因为数组对象放置在堆中的不同位置)
  • 增加的内存占用量(因为数组中引用的所有对象都是对象,因此拥有附加的Class和同步信息)。

内联类

在Java社区内,现在正在付出很大的努力来引入“内联类”(以前称为“值类”)。 这项工作的最新状态(截至2019年7月)由Brian Goetz i提出。
在此视频中,标题为“ Project Valhalla Update(2019版)”。 没有人知道何时在正式的Java版本中提供此功能。 我个人的猜测是2021年以后的某个时候。

一旦此功能可用,以下是如何排列嵌入式Point对象的数组:

 Array  +======+  |Header|  +------+  |x    |  +------+  |y    |  +------+  |x    |  +------+  |y    |  +------+  |...  |  +------+ 

可以看出,该方案消耗更少的内存(没有Point头),提高了局部性(数据按顺序放置在内存中),并且可以直接访问数据而无需遵循间接引用指针。 另一方面,我们丢失了对象身份的概念,本文稍后将对此进行讨论。

模拟一些内联类属性

在下面,我们将对内联类的某些属性进行仿真。 应当注意,下面的所有示例现在都可以在标准Java 8和更高版本上运行。

假设我们有一个interface Point带有X和Y吸气剂,如下所述:

 public interface Point { int x(); int y(); } y(); } 

然后,我们可以轻松地创建一个不变的实现
Point界面如下图所示:

 public final class VanillaPoint implements Point { private final int x, y; public VanillaPoint( int x, int y) { this .x = x; this .y = y; } @Override public int x() { return x; } x; } @Override public int y() { return y; } y; } // toString(), equals() and hashCode() not shown for brevity  } 

此外,假设我们愿意放弃数组中Point对象的Object / identity属性。 这意味着,除其他外,我们无法同步或执行身份操作(例如==System::identityHashCode

这里的想法是创建一个内存区域,我们可以直接在字节级别使用该内存区域,并在那里将对象展平。 可以将这个内存区域封装在一个名为InlineArray<T>的通用类中,如下所示:

 public final class InlineArray<T> { private final ByteBuffer memoryRegion; private final int elementSize; private final int length; private final BiConsumer<ByteBuffer, T> deconstructor; private final Function<ByteBuffer,T> constructor; private final BitSet presentFlags; public InlineArray( int elementSize, int length, BiConsumer<ByteBuffer, T> deconstructor, Function<ByteBuffer,T> constructor ) { this .elementSize = elementSize; this .length = length; this .deconstructor = requireNonNull(deconstructor); this .constructor = requireNonNull(constructor); this .memoryRegion = ByteBuffer.allocateDirect(elementSize * length); this .presentFlags = new BitSet(length); } public void put( int index, T value) { assertIndexBounds(index); if (value == null ) { presentFlags.clear(index); } else { position(index); deconstructor.accept(memoryRegion, value); presentFlags.set(index); } } public T get( int index) { assertIndexBounds(index); if (!presentFlags.get(index)) { return null ; } position(index); return constructor.apply(memoryRegion); } public int length() { return length; } private void assertIndexBounds( int index) { if (index < 0 || index >= length) { throw new IndexOutOfBoundsException( "Index [0, " + length + "), was:" + index); } } private void position( int index) { memoryRegion.position(index * elementSize); }  } 

请注意,此类可以处理任何类型的元素( T类型),但前提是它具有最大的元素大小,但可以将其解构(序列化)为字节。 如果所有元素的元素大小都与Point相同,则该类效率最高(即始终为Integer.BYTES * 2 = 8字节)。 还要注意,该类不是线程安全的,但是可以添加该类以增加内存屏障为代价,并且根据解决方案使用ByteBuffer单独视图。

现在,假设我们要分配一万个点的数组。 有了新的InlineArray类,我们可以这样进行:

 public class Main { public static void main(String[] args) { InlineArray<Point> pointArray = new InlineArray<>( Integer.BYTES * 2 , // The max element size 10_000, (bb, p) -> {bb.putInt(px()); bb.putInt(py());}, bb -> new VanillaPoint(bb.getInt(), bb.getInt()) ); Point p0 = new VanillaPoint( 0 , 0 ); Point p1 = new VanillaPoint( 1 , 1 ); pointArray.put( 0 , p0); // Store p0 at index 0 pointArray.put( 1 , p1); // Store p1 at index 1 System.out.println(pointArray.get( 0 )); // Should produce (0, 0) System.out.println(pointArray.get( 1 )); // Should produce (1, 1) System.out.println(pointArray.get( 2 )); // Should produce null }  } 

如预期的那样,代码在运行时将产生以下输出:

 VanillaPoint{x= 0 , y= 0 }  VanillaPoint{x= 1 , y= 1 }  null 

请注意,我们如何向InlineArray提供元素解构函数和元素构造函数,以告知其应如何解构和构造
Point对象指向线性存储器或从线性存储器Point对象。

仿真属性

上面的模拟可能不会获得与真正的内联类相同的性能提升,但是在内存分配和位置方面的节省将是大致相同的。 上面的模拟是分配堆外内存,因此您的垃圾回收时间将不受InlineArray放置的元素数据的InlineArrayByteBuffer中的元素的布局就像建议的内联类数组一样:

 Array  +======+  |Header|  +------+  |x    |  +------+  |y    |  +------+  |x    |  +------+  |y    |  +------+  |...  |  +------+ 

由于我们使用ByteBuffer被索引与对象
int ,后备存储区域被限制为2 ^ 31个字节。 例如,这意味着我们只能将2 ^(31-3)= 2 ^ 28≈2.68亿
在我们用尽地址空间之前,数组中的Point元素(因为每个点占用2 ^ 3 = 8个字节)。 实际的实现可以通过使用多个ByteBuffer,Unsafe或Chronicle Bytes之类的库来克服此限制。

懒惰的实体

给定InlineArray类,从中提供元素非常容易
InlineArray是惰性的,在某种意义上,当从数组中检索元素时,它们不必急于反序列化所有字段。 这是可以做到的:

首先,我们创建Point接口的另一种实现,该实现从后备ByteBuffer本身而不是本地字段中获取其数据:

 public final class LazyPoint implements Point { private final ByteBuffer byteBuffer; private final int position; public LazyPoint(ByteBuffer byteBuffer) { this .byteBuffer = byteBuffer; this .position = byteBuffer.position(); } @Override public int x() { return byteBuffer.getInt(position); } @Override public int y() { return byteBuffer.getInt(position + Integer.BYTES); } // toString(), equals() and hashCode() not shown for brevity  } 

然后,我们只需要替换粘贴到
InlineArray是这样的:

 InlineArray pointArray = new InlineArray<>( Integer.BYTES * 2 , 10_000, (bb, p) -> {bb.putInt(px()); bb.putInt(py());}, LazyPoint:: new // Use this deserializer instead  ); 

如果使用与上述相同的主要方法,将产生以下输出:

 LazyPoint{x= 0 , y= 0 }  LazyPoint{x= 1 , y= 1 }  null 

凉。 这对于具有数十个甚至数百个字段的实体特别有用,因为对于其中的问题,只能访问字段的有限子集。

这种方法的缺点是,如果在我们的应用程序中仅保留一个LazyPoint引用,则它将阻止整个后备ByteBuffer垃圾回收。 因此,像这样的任何惰性实体都最好用作短期对象。

使用大量数据

如果我们想使用非常大的数据集合(例如,以TB为单位),可能来自数据库或文件,并将其有效地存储在JVM内存中,然后能够与这些集合一起使用以提高计算性能,该怎么办? 我们可以使用这种技术吗?

Speedment HyperStream是一种利用类似技术能够将数据库数据作为标准Java Streams提供的产品,并且已经有一段时间了。 HyperStream可以按上述方式布置数据,并且可以在单个JVM中存储TB级的数据,而对Garbage Collection的影响很小或没有影响,因为数据是非堆存储的。 它可以使用就地反序列化直接从后备存储区域中获得单个字段,从而避免了不必要的实体完全反序列化。 它的标准Java流是确定性的超低延迟,在某些情况下可以在100 ns内构造和使用流。

这是在电影之间进行分页时如何在应用程序中使用HyperStream(实现标准Java Stream)的示例。 的
Manager films变量由Speedment自动提供:

 private Stream<Film> getPage( int page, Comparator<Film> comparator) { return films.stream() .sorted(comparator) .skip(page * PAGE_SIZE) .limit(PAGE_SIZE) } 

即使可能有数万亿的影片,该方法通常也将在不到一微秒的时间内完成,因为Stream直接连接到RAM并使用内存索引。

在此处阅读有关Speedment HyperStream性能的更多信息。

通过在此处下载Speedment HyperStream 来评估自己的数据库应用程序中的性能。

资源资源

瓦尔哈拉计划https://openjdk.java.net/projects/valhalla/

Speedment HyperStream https://www.speedment.com/hyperstream/ Speedment初始化程序https://www.speedment.com/initializer/

翻译自: https://www.javacodegeeks.com/2019/08/java-benefit-inline-class-properties-starting.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/341311.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1054 求平均值(PAT乙级 C++)

题目 本题的基本要求非常简单&#xff1a;给定 N 个实数&#xff0c;计算它们的平均值。但复杂的是有些输入数据可能是非法的。一个“合法”的输入是 [−1000,1000] 区间内的实数&#xff0c;并且最多精确到小数点后 2 位。当你计算平均值的时候&#xff0c;不能把那些非法的数…

ubuntu16.04下安装ibus拼音

按照网上给的安装方法&#xff0c;没成功&#xff0c;在切换ibus的时候总是报错&#xff0c;记录下解决办法。 安装语言包 System Settings–>Language Support–>Install/Remove Languages 选中chinese&#xff0c;点击Apply应用即可&#xff0c;等待下载安装完成。 …

1057 数零壹(PAT乙级 C++实现)

题目 给定一串长度不超过 10​5 的字符串&#xff0c;本题要求你将其中所有英文字母的序号&#xff08;字母 a-z 对应序号 1-26&#xff0c;不分大小写&#xff09;相加&#xff0c;得到整数 N&#xff0c;然后再分析一下 N 的二进制表示中有多少 0、多少 1。例如给定字符串 P…

解决Ubuntu中sublime无法输入中文的问题

主要目的&#xff1a; 安装 Sublime Text 3 安装 Fcitx 输入法 皮肤 修复 Sublime Text 3 在 Ubuntu(Debian) 系统下的无法输入中文(CJK 字符)输入法的问题 注意&#xff1a; 这个修复仅当在终端中使用 subl . 调用 Sublime Text 的时有效, 具体原因请看源代码src/s…

java编程访问hdfs_以编程方式访问Java基本类型的大小

java编程访问hdfs许多不熟悉Java的开发人员首先要了解的一件事是Java的基本原始数据类型 &#xff0c;其固定&#xff08;与平台无关&#xff09;的大小&#xff08;以位或字节为单位用二进制补码表示 &#xff09;以及它们的范围&#xff08;Java中所有数字类型都是带符号的&a…

1059 C语言竞赛(PAT乙级 C++)

题目 C 语言竞赛是浙江大学计算机学院主持的一个欢乐的竞赛。既然竞赛主旨是为了好玩&#xff0c;颁奖规则也就制定得很滑稽&#xff1a; 0、冠军将赢得一份“神秘大奖”&#xff08;比如很巨大的一本学生研究论文集……&#xff09;。1、排名为素数的学生将赢得最好的奖品 —…

【Ubuntu】Ubuntu16.04安装 搜狗输入法 史上最详细

一、基本配置 开发环境&#xff1a;ubuntu16.04 二、准备工作 下载 搜狗输入法linux版 搜狗输入法官网Linux版 我这里是64位的&#xff0c;所以选择64位 1、拷贝搜狗输入法到ubuntu cp /media/sf_share/6q/sogoupinyin_2.3.1.0112_amd64.deb . 2、安装搜狗输入法 首次安装会报…

Spring Boot和Hibernate:打印查询和变量

办公室已经很晚了&#xff0c;您陷入了这个带有JoinColumns和层叠的奇怪的Jpa代码中&#xff0c;而您找不到错误所在。 您希望有一种方法可以查看打印的查询以及值。 稍微调整一下Spring Boot应用程序就可以实现。 借助lombock&#xff0c;这是我们的jpa模型。 package com.g…

解决SSH登录无响应timed out问题

输入 ssh usernamexx.xx.xx.xx光标一直闪烁&#xff0c;直到提示连接超时timed out。 这是因为服务器端的防火墙禁用了ssh服务的端口&#xff0c;默认为22. 有两种解决方法&#xff1a; 1&#xff09;直接禁用防火墙 2&#xff09;只开启22号端口 以Ubuntu为例&#xff1a; 1&…

restful json_Dropwizard:轻松的RESTful JSON HTTP Web服务

restful json寻求快速&#xff0c; 轻松地创建可用于生产环境的RESTful JSON HTTP Web服务的Java开发人员应考虑使用Dropwizard框架。 Dropwizard汇集了相互补充的广受好评的库&#xff0c;因此您可以了解重要的内容&#xff1a;编写和交付工作代码。 对于那些对所用库的详细信…

1061 判断题(PAT乙级 C++)

题目 判断题的评判很简单&#xff0c;本题就要求你写个简单的程序帮助老师判题并统计学生们判断题的得分。 输入格式&#xff1a; 输入在第一行给出两个不超过 100 的正整数 N 和 M&#xff0c;分别是学生人数和判断题数量。第二行给出 M 个不超过 5 的正整数&#xff0c;是…

gem for onenote安装教程

https://www.xiazaiba.com/jiaocheng/52255.html

使用Java :: Geci生成setter和getter

在本文中 &#xff0c;我们创建了非常简单的hello-world生成器&#xff0c;以介绍框架以及通常如何生成生成器。 在本文中&#xff0c;我们将研究访问器生成器&#xff0c;它是在Java :: Geci的核心模块中定义的&#xff0c;它是商业级的&#xff0c;而不是仅用于演示的生成器。…

1062 最简分数(PAT乙级 C++)

题目 一个分数一般写成两个整数相除的形式&#xff1a;N/M&#xff0c;其中 M 不为0。最简分数是指分子和分母没有公约数的分数表示形式。 现给定两个不相等的正分数 N1/M​1​​ 和 N​2/M​2​​ &#xff0c;要求你按从小到大的顺序列出它们之间分母为 K 的最简分数。 输…

使用xshell上传本地文件到服务器

1.查看linux上是否安装rz工具 2&#xff0c;查看安装是否完成&#xff0c;然后上传文件到服务器

ubuntu镜像下载

官方下载地址&#xff08;不推荐&#xff09; https://www.ubuntu.com/download 推荐使用 Ubuntu 20.04.4 LTS (Focal Fossa) 其余下载地址 中科大源 http://mirrors.ustc.edu.cn/ubuntu-releases/16.04/ 阿里云开源镜像站 http://mirrors.aliyun.com/ubuntu-releases/…

Dev-C++ 一直提示源文件未编译,原因及解决办法

原因 编译命令换行&#xff08;可能有的同学和我一样喜欢换行写&#xff09;&#xff0c;导致编译通不过&#xff0c;如果选择编译运行&#xff0c;便会提示"源文件未编译"。 如图 解决办法 这种写法不对&#xff0c;正确如下&#xff08;两个命令间有一个空格&…

ubuntu 下安装qemu

&#xff08;1&#xff09;安装qemu 仿真ARM需要使用qemu-system-arm&#xff0c;安装模拟器 sudo apt-get install qemu-kvm qemu-kvm-extras &#xff08;2&#xff09;下载内核镜像。以下名称叫ubuntu.iso &#xff08;3&#xff09;创建一个虚拟磁盘 sudo qemu-im…

python 怎么查看变量的数据类型

方法 type(variable_name)示例 a hello b type(a) print(b)a 10 b type(a) print(b)结果 <class str> <class int>

Valhalla项目:LW2内联类型的初步了解

我总结了最近在Valhalla LW2 项目 “ 内联类型 ”中取得的一些进展&#xff0c;这些进展最近在我的博客文章“ Valhalla LW2进度-内联类型 ”中公开了。 在这篇文章中&#xff0c;我通过针对最近发布的Valhalla Early Access Build jdk-14-valhalla 1-8&#xff08;2019/7/4&a…