用 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

相關文章
相關標籤/搜索