本文将介绍如何进行 Java Lambdas 序列化性能检测、Lambdas 的重要性以及 Lambdas 在分布式系统中的应用。html
Lambdas 表达式是 Java 8 中万众期待的新特性,其若干用途包括:java
为匿名内部类减小所需样本代码。服务器
缩小值的做用域。Lambdas 表达式中的 this 不会涉及到外部类,减小了内存泄露。网络
轻松集成现有 API 与新的 Streams API。数据结构
Lambdas 另外一个鲜有人知的特色就是可被序列化。app
序列化有益于对象的状态持久化和网络传输。 Lambdas 应该尽量无状态,这样就能够保存 Lambdas ,但这并非典型的用例。分布式
Lambdas 表达式的目的是给程序库传递代码片断,使之与库中正在运行的程序交互。可是若是程序库支持像 Chronicle Engine 这样的分布式系统,那又会怎么样?ide
Chronicle Engine 是一个库,可以让用户使用各自的应用程序远程访问数据结构,不管是用 Java、C# 客户端,仍是用 NFS 文件系统。该库还支持存储和持久化数据,也支持复制。函数
对于某些局部运行的操做,使用 Lambdas 执行不失为一种简单可行的方法。示例操做以下:性能
MapView<String, Long> map = acquireMap(“map-name”, String.class, Long.class); map.put(“key”, 1); long ret = map.applyToKey(“key”, v -> v + 1); // ret == 2
这里没有必要知道数据的具体存储位置,若是是远程服务器,就会在那台服务器上对 Lambda 序列化,而后执行,并将结果返回。
上图显示了 OneAPM 如何监控和让 Java 应用之间的调用关系可视化。
不获取字段的 Lambda 能够被 Java 更高效地处理,由于每一个实例都同样,因此并不须要每次都建立新的对象。可是,若编译时 Lambda 获取到未知值,就须要建立新的对象,并将获取的值保存。
Non capturing Lambda
Function<String, String> appendStar = s -> s + "*"
Capturing Lambda
String star = "*"; Function<String, String> appendStar = s -> s + star;
Lambdas 默认是不可序列化的,必须实现一种可序列化的接口。可使用强制转换把接口类型转换为所需类型的一种方式。
Function<String, String> appendStar = (Function<String, String> & Serializable) (s -> s + star);
笔者我的不喜欢这样作,由于这样会破坏减小代码的目标。一个解决的方法就是定义本身所需的可序列化的接口。
@FunctionalInterface public interface SerializableFunction<I, O> extends Function<I, O>, Serializable {
这就须要以下所写:
SerializableFunction<String, String> appendStar = s -> s + star;
或者按照这种方法写入:
<R> R applyToKey(K key, @NotNull SerializableFunction<E, R> function) {
该库的调用者就能够以下所写,而不须要任何样本代码。
String s = map.applyToKey(“key”, s-> s + “*”);
利用序列化的 Lambdas,可进行以下所示的实时查询:
// print the last name of all the people in NYC acquireMap(“people”, String.class, Person.class).query() .filter(p -> p.getCity().equals(“NYC”)) // executed on the server .map(p → p.getLastName()) // executed on the server .subscribe(System.out::println); // executed on the client.
可查询接口是必需的,所以过滤器 Predicate 和 map 函数也必须隐式可序列化。若是须要使用 Streams API,那就要使用早期较为复杂的数据类型转换函数 cast。
笔者曾经在一个字符串中写入符号“*”,并使用 JMH 对简单的序列化的和反序列化的 Lambda 进行时延采样,而后比较采集和非采集两种状况下的时延,发送枚举时两种状况下的时延也一并比较。代码和结果以下表所示:
99.99%的时延意味着该试验的99.99%都是在时延之中。时间都用微秒计算。
Test | Typical latency | 99.99% latency |
---|---|---|
Java Serialization, non-capturing | 33.9 µs | 215 µs |
Java Serialization, capturing | 36.3 µs | 704 µs |
Java Serialization, with an enum | 7.9 µs | 134 µs |
Chronicle Wire (Text), non-capturing | 20.4 µs | 147 µs |
Chronicle Wire (Text), capturing | 22.5 µs | 148 µs |
Chronicle Wire (Text), with an enum | 1.2 µs | 5.9 µs |
Chronicle Wire (Binary), non-capturing | 11.7 µs | 103 µs |
Chronicle Wire (Binary), capturing | 12.7 µs | 135 µs |
Chronicle Wire (Binary), with an enum | 1.0 µs | 1.2 µs |
使用 Lambda 是很简单,但它效率不高时,就须要找一个备选方案。因此当 Lambda 的使用形成性能问题时,就要使用备选方案。
enum Functions implements SerializableFunction<String, String> { APPEND_STAR { @Override public String apply(String s) { return s + '*'; } } }
为考察使用枚举所起到的做用,能够比较发送到服务器的数据量的多少,在那里能够看到全部序列化的数据。
下面就是当在 TextWire 中序列化时,非采集的 Lambda 的状况。(基于 YAML)
!SerializedLambda { cc: !type lambda.LambdaSerialization, fic: net/openhft/chronicle/core/util/SerializableFunction, fimn: apply, fims: (Ljava/lang/Object;)Ljava/lang/Object;, imk: 6, ic: lambda/LambdaSerialization, imn: lambda$textWireSerialization$ea1ad110$1, ims: (Ljava/lang/String;)Ljava/lang/String;, imt: (Ljava/lang/String;)Ljava/lang/String;, ca: [ ] }
枚举序列化以下所示:
!Functions APPEND_STAR
注意:当须要采集某些值时,不可使用枚举。咱们要作的就是让你经过传递有附加参数的枚举,以得到最有效的组合。
用枚举代替 Lambdas 的一个好处就是,能够跟踪全部功能客户执行和整合的方式。某些函数使用普遍,运用枚举使得修复任一单独函数中产生的bug更为容易,所以会被常用。举一个简单的例子,MapFunction 起初有不少不一样的 Lambdas,而如今已经被归为一类。
若是所使用的 API 支持,可将 Lambdas 用于分布式应用程序。若是须要附加的性能,也可使用枚举。
原文地址:https://dzone.com/articles/measuring-the-serialization-performance-of-lambdas