經(jīng)過一年的開發(fā)跃须,Hprose 3.0 for .NET 的序列化反序列化部分終于基本上完成了续语。
這次升級(jí)是完全重寫了 Hprose for .NET 的代碼邀窃。
之前的 Hprose 1.x for .NET 兼容 .NET 所有的平臺(tái)版本氨菇,包括 .NET Framework 诀姚、.NET Compact Framework响牛、.NET Micro Framework、SilverLight赫段、Windows Phone呀打、Mono、.NET Core 等糯笙。
這次升級(jí)取消了對(duì)一些過時(shí)的 .NET 平臺(tái)的支持贬丛。僅保留了對(duì) .NET 3.5 Compact Framework、.NET 4.0+给涕、.NET Core 2.0+豺憔、.NETStandard 2.0(包含 Android、iOS够庙、Mac 平臺(tái))的支持恭应。
這次升級(jí)后的代碼,使用了最新版本的 C# 的語法來編寫首启,代碼在可讀性和性能上較之之前的版本都有了極大的改進(jìn)。
下面我們就來看看 Hprose 3.0 for .NET 序列化究竟有多快撤摸。
首先來看一下對(duì)象數(shù)組序列化反序列化性能對(duì)比毅桃,測(cè)試代碼為:BenchmarkObjectSerialize.cs,測(cè)試結(jié)果如下表所示:
Hprose 3.0 相對(duì)于 1.x 相比准夷,增加了對(duì) DataSet钥飞、DataTable 序列化和反序列化的支持。下面是 DataSet 序列化反序列化性能對(duì)比衫嵌,測(cè)試代碼為:BenchmarkDataSetSerialize.cs读宙,測(cè)試結(jié)果如下表所示:
從上面兩個(gè)圖表可以看出,雖然 Newton Json 的序列化反序列化性能跟 .NET 自帶的 DataContract 相比已經(jīng)高出很多楔绞,但是 Hprose 比 Newton Json 還要快 1 倍左右结闸。這是怎么做到的呢?下面我們就來詳細(xì)剖析一下酒朵。
泛型序列化器和反序列化器
在 Hprose 1.x for .NET 中桦锄,序列化和反序列化的代碼主要是在 HproseWriter
和 HproseReader
兩個(gè)類中實(shí)現(xiàn)的。
而 Hprose 3.0 for .NET 中蔫耽,序列化和反序列化的代碼則分別放在 Hprose.IO.Serializers
和 Hprose.IO.Deserializers
兩個(gè)名稱空間下面结耀,并且定義了兩個(gè)抽象的泛型類 Serializer<T>
和 Deserializer<T>
來負(fù)責(zé)序列化和反序列化。
每種具體的數(shù)據(jù)類型的序列化都由一個(gè)具體的序列化器來實(shí)現(xiàn),反序列化則由一個(gè)具體的反序列化器來實(shí)現(xiàn)图甜。
具體的序列化器和反序列化器通過 Serializer<T>
和 Deserializer<T>
的 Instance
屬性來獲得碍粥。
基本類型和幾個(gè)常用類型的序列化器、反序列化器被注冊(cè)在 Serializer
和 Deserializer
這兩個(gè)非泛型類的靜態(tài)初始化方法中黑毅,當(dāng)它們第一次被調(diào)用時(shí)會(huì)自動(dòng)初始化嚼摩。
而對(duì)于數(shù)組、枚舉博肋、容器和自定義類型低斋,則會(huì)在泛型序列化器和反序列化器的 Instance
屬性第一次被調(diào)用時(shí)初始化。
通過這種方式匪凡,實(shí)現(xiàn)代碼不但變得更清晰易懂膊畴,而且更便于擴(kuò)展。
另外還有一個(gè)附加的好處病游,就是當(dāng)知道要序列化或反序列化的具體類型時(shí)唇跨,序列化器和反序列化器可以直接通過泛型類的 Instance
屬性獲取到,從而省去了判斷查找的時(shí)間衬衬。
序列化器和反序列化器除了直接緩存在泛型類的 Instance
屬性中以外买猖,還在非泛型的 Serializer
和 Deserializer
類中通過 ConcurrentDictionary
靜態(tài)字段容器做了緩存。
雖然通過 ConcurrentDictionary
這種緩存方式要比直接通過泛型類的 Instance
屬性來獲取序列化器和反序列化器在速度上慢幾十納秒滋尉,但是對(duì)于無法在編譯期就能獲取到具體類型的數(shù)據(jù)來說玉控,這仍然是最快速的獲取序列化器和反序列化器的方式。
除了對(duì)序列化器和反序列化器采用了這種特化泛型類 + ConcurrentDictionary
的雙緩存模式以外狮惜,Hprose 在屬性字段存取器高诺、類型轉(zhuǎn)換器等實(shí)現(xiàn)上也采用了這種方式。
這是 Hprose 3.0 for .NET 序列化和反序列化性能提高的最主要原因之一碾篡。
通過表達(dá)式樹來存取字段和屬性
在 Hprose 1.x for .NET 中虱而,對(duì)于自定義類型的字段和屬性的存取,根據(jù)不同的平臺(tái)采用了直接反射和 Emit 生成代碼兩種方式开泽。
在 Hprose 3.0 for .NET 中牡拇,則統(tǒng)一使用了表達(dá)式樹生成代碼的方式。表達(dá)式樹生成的代碼跟使用 Emit 生成的代碼穆律,在執(zhí)行效率上是沒有差別的惠呼。但是在實(shí)現(xiàn)上,表達(dá)式樹實(shí)現(xiàn)的代碼具有更好的可讀性峦耘。
另外罢杉,對(duì)于表達(dá)式樹生成的代碼也做了雙緩沖,因此序列化反序列化自定義對(duì)象的執(zhí)行效率幾乎可以達(dá)到甚至超過硬編碼的效率贡歧。
通過表達(dá)式樹來創(chuàng)建對(duì)象
在 C# 中創(chuàng)建一個(gè)對(duì)象滩租,可以通過 new
關(guān)鍵字來創(chuàng)建赋秀,也可以通過反射的方式來創(chuàng)建。跟通過反射創(chuàng)建對(duì)象相比律想,new
一個(gè)對(duì)象顯然要快的多猎莲。
但是創(chuàng)建泛型對(duì)象是個(gè)特例。例如:
public T New<T>() where T : new() => new T();
這個(gè)方法技即,它在調(diào)用時(shí)著洼,new T()
生成的 IL 代碼實(shí)際上跟:
Activator.CreateInstance<T>();
是差不多的。
也就是說而叼,雖然代碼中寫的是 new T()
身笤,但是實(shí)際上調(diào)用的卻是 Activator.CreateInstance<T>()
。
Hprose 中為了更快的創(chuàng)建泛型對(duì)象葵陵,使用了下面這個(gè)泛型對(duì)象創(chuàng)建工廠:
public static class Factory<T> {
private static readonly Func<T> constructor = GetConstructor();
private static Func<T> GetConstructor() {
try {
return Expression.Lambda<Func<T>>(Expression.New(typeof(T))).Compile();
}
catch {
return () => (T)Activator.CreateInstance(typeof(T), true);
}
}
public static T New() {
return constructor();
}
}
該工廠類通過表達(dá)式樹來生成創(chuàng)建對(duì)象的代碼液荸,表達(dá)式樹生成的代碼跟直接 new
具體類型是一樣的,速度上比 Activator.CreateInstance<T>()
要快 2 - 3 倍(在 Mono 平臺(tái)上甚至?xí)鞄资叮┩迅荨V挥挟?dāng)表達(dá)式樹創(chuàng)建失敗時(shí)娇钱,才會(huì)使用 Activator.CreateInstance
作為代替方案。另外绊困,這里使用的是 Activator.CreateInstance(typeof(T), true)
文搂,這樣不但在性能上比 Activator.CreateInstance<T>()
快幾納秒,而且它還可以創(chuàng)建只有非 public
無參構(gòu)造器的類的對(duì)象秤朗。
最新版本的代碼可以在 github 的 hprose/hprose-dotnet 中查看煤蹭。如果大家有更好的改進(jìn)方式,歡迎大家提交修改取视。