用 JMH 检测 Lambdas 序列化性能

本文将介绍如何进行 Java Lambdas 序列化性能检测、Lambdas 的重要性以及 Lambdas 在分布式系统中的应用。html

Lambdas 表达式是 Java 8 中万众期待的新特性,其若干用途包括:java

  1. 为匿名内部类减小所需样本代码。服务器

  2. 缩小值的做用域。Lambdas 表达式中的 this 不会涉及到外部类,减小了内存泄露。网络

  3. 轻松集成现有 API 与新的 Streams API。数据结构

Lambdas 另外一个鲜有人知的特色就是可被序列化。app

为何对 Lambda 序列化?

序列化有益于对象的状态持久化和网络传输。 Lambdas 应该尽量无状态,这样就能够保存 Lambdas ,但这并非典型的用例。分布式

Lambdas 表达式的目的是给程序库传递代码片断,使之与库中正在运行的程序交互。可是若是程序库支持像 Chronicle Engine 这样的分布式系统,那又会怎么样?ide

什么是 Chronicle Engine?

Chronicle Engine 是一个库,可以让用户使用各自的应用程序远程访问数据结构,不管是用 Java、C# 客户端,仍是用 NFS 文件系统。该库还支持存储和持久化数据,也支持复制。函数

分布式系统中的 Lambdas

对于某些局部运行的操做,使用 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 序列化,而后执行,并将结果返回。

用 JMH 检测 Lambdas 序列化性能

上图显示了 OneAPM 如何监控和让 Java 应用之间的调用关系可视化。

Capturing Lambda

不获取字段的 Lambda 能够被 Java 更高效地处理,由于每一个实例都同样,因此并不须要每次都建立新的对象。可是,若编译时 Lambda 获取到未知值,就须要建立新的对象,并将获取的值保存。

Non capturing Lambda

Function<String, String> appendStar = s -> s + "*"

Capturing Lambda

String star = "*";
    Function<String, String> appendStar = s -> s + star;

可序列化的 Lambdas

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 的实时查询

利用序列化的 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。

序列化 Lambdas 的性能

笔者曾经在一个字符串中写入符号“*”,并使用 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

相关文章
相关标签/搜索