自然的基本规则,例如光速和一般信息理论,对我们从传统系统架构中获得的最大性能设置了重大限制。了解作为Java开发人员,您可以使用in-JVM技术和Java Streams以数量级提高性能。
例如,如果应用服务器和数据库服务器相距100米(约330英尺),则光速所引起的往返延迟略微超过600 ns。更重要的是,由于TCP / IP协议处理,10 GBit / s连接上的单个数据包往返延迟几乎不能优化到低于25 us(= 25,000 ns),尽管采用黑带技巧如定制内核构建,忙碌轮询和CPU亲和力。
在本文中,我将展示如何使用in-JVM内存技术直接从RAM创建Java Streams。我们将使用名为Speedment的基于Stream的Java ORM,它可以使用标准java.util.stream.Stream对象执行数据分析,以及如何在200 ns内创建和完成其中一些流,这令人惊讶地只是CPU访问64的延迟的两倍。位主存。
200 ns比远程数据库(100 m)的理论最小延迟快125倍,其内部处理延迟为零,并且单个TCP数据包可以传达查询和响应。在实时场景中,数据库的内部处理延迟从不为零,并且查询和结果通常在多个TCP包中发送。因此,在许多情况下,加速因子可能是1,000倍或更多。
数据库
在下面的示例中,我们使用来自Sakila数据库内容的MySQL数据。Sakila是一个模拟电影租赁店的示例数据库。它有表格,电影,演员,类别等,可以在这里免费下载。应该注意的是,这是一个小型数据库,但事实证明,许多Speedment流操作都是O(1)或者O(log(N())在复杂性方面,从而确保了相同的速度,无论数据集有多大或多小。
第1步:创建项目
首先,我们需要配置我们的pom.xml-file以使用最新的Speedment依赖项和Maven插件。最快的方法是pom.xml使用您可以在此处找到的Speedment Initializer 生成-file 。首先,选择数据库类型“MySQL”并确保启用“内存加速”,然后按“下载”,您将获得一个整个项目文件夹,其中包含一个Main.java自动生成的文件。
接下来,解压缩项目文件夹zip文件,打开命令行,转到解压缩文件夹(pom.xml文件所在的位置)并输入以下命令:
mvn speedment:工具
接下来,连接到数据库并开始:
第2步:生成代码
从数据库加载架构数据后,可以通过按“生成”按钮生成完整的Java域模型。
第3步:编写应用程序代码
要使用Speedment,首先需要创建Speedment实例。这可以通过使用在步骤2中与域模型一起自动生成的构建器来完成。打开Main.java文件并main()使用此代码段替换方法中的代码:
Speedment app = new SakilaApplicationBuilder()
//用您自己的密码替换它
。withPassword(“sakila-password”)
//启用in-JVM内存加速
//通过注释掉这一行,我们可以禁用加速
。withBundle(InMemoryBundle。类)
。build();
//如果将数据从数据库加载到快照视图中
//我们已经安装了In-JVM-Acceleration
app。得到(DataStoreComponent。类)
。ifPresent(DataStoreComponent :: load);
作为基本功能的演示,我们将首先编写一个只打印出所有电影的应用程序:
//获得允许我们使用的FilmManager
//使用“电影”表
FilmManager 电影 = app。getOrThrow(FilmManager。类);
//创建电影和打印流
//每部电影
电影。流()
。的forEach(系统。出来 :: 的println);
上面的代码将产生以下输出(为简洁起见缩短):
FilmImpl { filmId = 1,title = ACADEMY DINOSAUR,…,length = 86,…}
FilmImpl { filmId = 2,title = ACE GOLDFINGER,…,length = 48,…}
FilmImpl { filmId = 3,title = ADAPTATION HOLES,…,length = 50,…}
…
第3步:使用过滤器
Speedment流支持所有流操作,包括过滤器。假设我们只想过滤超过60分钟的电影,并计算我们有多少次出现。这可以这样完成:
电影。流()
。过滤器(电影。LENGTH。GREATERTHAN(60))
。count();
系统。出。格式(“有%,d电影超过60分钟。”,计数);
这将产生以下输出:
有超过60分钟的896部电影
可以将任意数量的过滤器应用于流,并且filter()可以使用and() / or()运算符来组合提供给方法的谓词。
第4步:设置JMH
到目前为止,我们还没有看到任何绩效数据。我们将在本文中使用JMH进行基准测试。JMH是一种Java工具,用于构建,运行和分析用Java和其他语言编写的基于JVM的基准测试。
我们将使用两种流类型进行性能测量:
一个相当简单的流,我们计算评级等于PG-13“过滤和计数” 的电影
一个更复杂的流,我们按LENGTH顺序(降序)对所有电影进行排序,然后我们跳过前745部电影,然后处理以下5部电影,我们从这五部电影中提取出租持续时间,最后我们计算这些整数的统计数据(即最小值,最大值和平均值)。这种类型称为“复杂”。
以下代码摘录显示了我们即将运行的基准:
private static final 谓词 RATING_EQUALS_PG_13 = 电影。评分。等于(评分。PG13); private static final Comparator LENGTH_DESCENDING = 电影。长度。反转(); @Benchmark public long filterAndCount(){ 返回 电影。stream()。过滤器(RATING_EQUALS_PG_13)。count(); } @Benchmark public IntSummaryStatistics complex(){ return films。stream()。排序(LENGTH_DESCENDING)。跳过(745)。限制(5)。mapToInt(电影。RENTAL_DURATION。asInt中())。summaryStatistics(); }
以下设置用于单线程延迟测量:
# 江铃控股 版本:1.21
# VM 版本:JDK 10,爪哇 的HotSpot(TM)64 - 位 服务器 VM,10 + 46
# VM 调用:/ 图书馆/ 的Java / JavaVirtualMachines / JDK - 10 JDK / 内容/ 首页/ 斌/ java的
# VM 选项:- javaagent:/ 应用/ 的IntelliJ IDEA CE。app / Contents / lib / idea_rt。jar = 63173:/ Applications / IntelliJ IDEA CE。app / Contents / bin - Dfile。encoding = UTF - 8
# 预热:5 次迭代,每次10 秒
# 测量:5 次迭代,每次10 秒
# 超时:每次迭代10 分钟
# 主题:1 线,将 同步 迭代
# 基准 模式:平均 时间,时间/ 运算
# 基准:COM。例子。板凳。复杂
使用SQL与MySQL数据库的流
在我的笔记本电脑(MacBook Pro,2015年中,2.2 GHz Intel Core i7,16 GB RAM)上对标准MySQL数据库(版本5.7.16)运行这些查询将产生如下所示的输出:
单线程延迟(低是更好)
基准 模式 Cnt评分误差单位
Bench .complex avgt 5 0.003 ± 0.001 s / op
Bench .filterAndCount avgt 5 0.001 ± 0.001 s / op
多线程的吞吐量(更高的是更好)
基准 模式 Cnt评分误差单位
Bench .complex thrpt 5 1714.980 ± 343.655 ops / s
Bench .filterAndCount 数据为 3 3154.984 ± 318.881 ops / s
通过MySQL数据库使用In-JVM内存加速的流
启用in-JVM内存加速并在我的笔记本电脑上再次运行相同的基准测试产生以下结果:
单线程延迟(低是更好)
基准 模式 Cnt评分误差单位
长凳.complex avgt 5个 ≈ 10 ⁻⁶S /运算
长凳.filterAndCount avgt 5 ≈ 10 ⁻⁷S /运算
多线程的吞吐量(更高的是更好)
基准 模式 Cnt评分误差单位
Bench .complex thrpt 5 4793915.881 ± 374680.158 ops / s
Bench .filterAndCount 数据 1 16958800.191 ± 1023015.568 ops / s
能够在旧笔记本电脑上每秒生产和消耗近1700万条流是非常惊人的。具有许多CPU内核的现代服务器级计算机每秒可以轻松生成和消耗超过2500万个流。
延迟的JMH时间分辨率不足以足够准确地进行测量。通过使用一个线程运行吞吐量测试并反转结果,平均滤波器和计数延迟估计为1 / 5,564,678 = 180 ns。这种更准确的延迟估计可以提供大约5,000而不是10,000的估计性能提升因子。
结论
启用in-JVM内存加速可以显着提高性能。在上面的基准测试中:
单线程延迟减少了一个因素:
复杂:~3,000
过滤器和计数:~5,000
多线程吞吐量增加了一个因素:
复杂:2,700
过滤器和计数:5,300
举例来说,这意味着具有一百万个子查询的复合JVM操作将使其聚合数据延迟从1小时减少到1秒。
笔记
对于SQL性能,流(自动)呈现给SQL查询。以下是渲染的Filter和Count SQL查询的外观:
SELECT COUNT(*)FROM(
选择
film_id
,title
,description
,
release_year
,language_id
,original_language_id
,
rental_duration
,rental_rate
,length
,
replacement_cost
,rating
,special_features
,
last_update
从
sakila
.film
哪里
(rating
= ? COLLATE utf8_bin)
)AS A.
,价值观:[PG- 13 ]
为评级列定义了一个索引。
可以看出,所有计数都是在数据库端完成的,并且流没有将任何不必要的Film对象从数据库引入JMH应用程序。