本文將介紹如何進行 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