Hprose 3.0 for .NET 的序列化为何这么快

经过一年的开发,Hprose 3.0 for .NET 的序列化反序列化部分终于基本上完成了。

这次升级是完全重写了 Hprose for .NET 的代码。

之前的 Hprose 1.x for .NET 兼容 .NET 所有的平台版本,包括 .NET Framework 、.NET Compact Framework、.NET Micro Framework、SilverLight、Windows Phone、Mono、.NET Core 等。

这次升级取消了对一些过时的 .NET 平台的支持。仅保留了对 .NET 3.5 Compact Framework、.NET 4.0+、.NET Core 2.0+、.NETStandard 2.0(包含 Android、iOS、Mac 平台)的支持。

这次升级后的代码,使用了最新版本的 C# 的语法来编写,代码在可读性和性能上较之之前的版本都有了极大的改进。

下面我们就来看看 Hprose 3.0 for .NET 序列化究竟有多快。

首先来看一下对象数组序列化反序列化性能对比,测试代码为:BenchmarkObjectSerialize.cs,测试结果如下表所示:

Hprose 3.0 相对于 1.x 相比,增加了对 DataSet、DataTable 序列化和反序列化的支持。下面是 DataSet 序列化反序列化性能对比,测试代码为:BenchmarkDataSetSerialize.cs,测试结果如下表所示:

从上面两个图表可以看出,虽然 Newton Json 的序列化反序列化性能跟 .NET 自带的 DataContract 相比已经高出很多,但是 Hprose 比 Newton Json 还要快 1 倍左右。这是怎么做到的呢?下面我们就来详细剖析一下。

泛型序列化器和反序列化器

在 Hprose 1.x for .NET 中,序列化和反序列化的代码主要是在 HproseWriterHproseReader 两个类中实现的。

而 Hprose 3.0 for .NET 中,序列化和反序列化的代码则分别放在 Hprose.IO.SerializersHprose.IO.Deserializers 两个名称空间下面,并且定义了两个抽象的泛型类 Serializer<T>Deserializer<T> 来负责序列化和反序列化。

每种具体的数据类型的序列化都由一个具体的序列化器来实现,反序列化则由一个具体的反序列化器来实现。

具体的序列化器和反序列化器通过 Serializer<T>Deserializer<T>Instance 属性来获得。

基本类型和几个常用类型的序列化器、反序列化器被注册在 SerializerDeserializer 这两个非泛型类的静态初始化方法中,当它们第一次被调用时会自动初始化。

而对于数组、枚举、容器和自定义类型,则会在泛型序列化器和反序列化器的 Instance 属性第一次被调用时初始化。

通过这种方式,实现代码不但变得更清晰易懂,而且更便于扩展。

另外还有一个附加的好处,就是当知道要序列化或反序列化的具体类型时,序列化器和反序列化器可以直接通过泛型类的 Instance 属性获取到,从而省去了判断查找的时间。

序列化器和反序列化器除了直接缓存在泛型类的 Instance 属性中以外,还在非泛型的 SerializerDeserializer 类中通过 ConcurrentDictionary 静态字段容器做了缓存。

虽然通过 ConcurrentDictionary 这种缓存方式要比直接通过泛型类的 Instance 属性来获取序列化器和反序列化器在速度上慢几十纳秒,但是对于无法在编译期就能获取到具体类型的数据来说,这仍然是最快速的获取序列化器和反序列化器的方式。

除了对序列化器和反序列化器采用了这种特化泛型类 + ConcurrentDictionary 的双缓存模式以外,Hprose 在属性字段存取器、类型转换器等实现上也采用了这种方式。

这是 Hprose 3.0 for .NET 序列化和反序列化性能提高的最主要原因之一。

通过表达式树来存取字段和属性

在 Hprose 1.x for .NET 中,对于自定义类型的字段和属性的存取,根据不同的平台采用了直接反射和 Emit 生成代码两种方式。

在 Hprose 3.0 for .NET 中,则统一使用了表达式树生成代码的方式。表达式树生成的代码跟使用 Emit 生成的代码,在执行效率上是没有差别的。但是在实现上,表达式树实现的代码具有更好的可读性。

另外,对于表达式树生成的代码也做了双缓冲,因此序列化反序列化自定义对象的执行效率几乎可以达到甚至超过硬编码的效率。

通过表达式树来创建对象

在 C# 中创建一个对象,可以通过 new 关键字来创建,也可以通过反射的方式来创建。跟通过反射创建对象相比,new 一个对象显然要快的多。

但是创建泛型对象是个特例。例如:

public T New<T>() where T : new() => new T();

这个方法,它在调用时,new T() 生成的 IL 代码实际上跟:

 Activator.CreateInstance<T>();

是差不多的。

也就是说,虽然代码中写的是 new T(),但是实际上调用的却是 Activator.CreateInstance<T>()

Hprose 中为了更快的创建泛型对象,使用了下面这个泛型对象创建工厂:

    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();
        }
    }

该工厂类通过表达式树来生成创建对象的代码,表达式树生成的代码跟直接 new 具体类型是一样的,速度上比 Activator.CreateInstance<T>() 要快 2 - 3 倍(在 Mono 平台上甚至会快几十倍)。只有当表达式树创建失败时,才会使用 Activator.CreateInstance 作为代替方案。另外,这里使用的是 Activator.CreateInstance(typeof(T), true),这样不但在性能上比 Activator.CreateInstance<T>() 快几纳秒,而且它还可以创建只有非 public 无参构造器的类的对象。

最新版本的代码可以在 github 的 hprose/hprose-dotnet 中查看。如果大家有更好的改进方式,欢迎大家提交修改。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容