仅具有一个元素的Java流有时会在应用程序中造成不必要的开销。 了解如何使用SingletonStream对象并为其中某些此类流获得十倍的性能,并了解如何同时简化代码。
背景
Java 8中的Stream
库是有史以来Java语言最强大的功能之一。 一旦您开始了解它的多功能性和所产生的代码可读性,您的Java代码样式将永远改变。 与其使用for
, if
和switch
语句以及众多中间变量来充斥所有琐碎的细节,您还可以使用Stream
,它仅包含要做什么的描述,而不是实际上是如何完成的。
几年前,我们不得不为Java项目做出API决定:我们应该为两种快速本地内存数据缓存方法选择哪种返回类型;
- 唯一的搜索键,返回一个值或不返回任何值
- 一个非唯一的搜索键,它返回任意数量的值(零到无穷大)。
这是最初的想法:
Optional<T> searchUnique(K key); // For unique keys
Stream<T> search(K key); // For non-unique keys
但是,我们希望这两个方法看起来完全相同,并且都返回一个
Stream<T>
。 然后,API看起来会更加整洁,因为唯一缓存将看上去与非唯一缓存完全相同。
但是,唯一搜索必须非常高效,并且每秒必须创建数百万个结果对象,而又不会产生太多开销。
解决方案
通过实现仅包含单个元素的SingletonStream
(因此,与具有任意数量元素的普通Stream
相比,可以进行高度优化),我们能够让这两种方法在保持性能的同时返回Stream
。 如果未找到键,则方法searchUnique(K key)
将返回一个空流( Stream.empty()
),如果该键存在,则它将返回SingletonStream
以及与该键关联的值。 我们会得到:
Stream<T> searchUnique(K key); // For unique keys
Stream<T> search(K key); // For non-unique keys
大! 我们可以吃饼干,仍然可以吃!
实施
SingletonStream
是Speedment Stream ORM的一部分,可以在GitHub上查看。 随意使用的Speedment自己的项目中使用Speedment任何它的组件的初始化 。
SingletonStream
是使用JVM的Escape Analysis进行堆栈分配的不错选择(在我以前的文章中, 这里和此处有关Escape Analysis的更多信息 )。 该实现有两种形式。 如果将STRICT值设置为true
,我们将获得一个完全惰性的Stream
,但是缺点是,一旦调用诸如.filter(), map()
等中间操作 ,我们将失去Singleton属性 。另一方面,将STRICT
值设置为false
, SingletonStream
会急切地执行许多中间操作 ,并且它将能够返回新的SingletonStream
从而保留Singleton属性 。 在许多情况下,这将提供更好的性能。
这里为参考流设计的解决方案也可以很容易地修改为单例流的原始形式。 因此,编写SingletonIntStream
,
SingletonLongStream
和SingletonDoubleStream
。 这是SingletonLongStream 。
应该注意的是,该类可以进一步发展,以便它可以支持惰性评估,同时始终保持高性能。 这是未来的工作。
性能
有多种方法可以测试SingletonStream
的性能,并将其与具有一个元素的标准Stream
实现进行比较。
这是使用JMH的一种方法。 第一个测试(计数)仅对流中的元素数进行计数,第二个测试(forEach)对流中的一个元素执行某些操作。
@Benchmark
public long singletonStreamCount() {return SingletonStream.of("A").count();
}@Benchmark
public long streamCount() {return Stream.of("A").count();
}@Benchmark
public void singletonStreamForEach() {SingletonStream.of("A").limit(1).forEach(blackHole());
}@Benchmark
public void streamForEach() {Stream.of("A").limit(1).forEach(blackHole());
}private static <T> Consumer<T> blackHole() {
return t -> {};
}
在MacBook Pro笔记本电脑上运行时,将产生以下结果:
...
Benchmark Mode Cnt Score Error Units
SingletonBench.singletonStreamCount thrpt 333419753.335 ops/s
SingletonBench.singletonStreamForEach thrpt 2312262034.214 ops/s
SingletonBench.streamCount thrpt 27453782.595 ops/s
SingletonBench.streamForEach thrpt 26156364.956 ops/s
...
“计数”操作的加速因子超过10。 对于“ forEach”操作,看来JVM能够完全优化SingletonStream
的完整代码路径。
测试一下
使用Speedment 初始化程序下载Speedment。
完整的测试课程在这里可用。
结论
SingletonStream
或多或少地作为扩展的Optional
起作用,并在保持Stream
库优点的同时提供高性能。
您可以通过将STRICT值设置为首选的严格性/性能选择来选择它的两个版本。
SingletonStream
可以进一步改进。
翻译自: https://www.javacodegeeks.com/2018/10/java-gain-performance-singletonstream.html