.Net 2.0: Entity as DTO vs Dataset as DTO / Xml Serialization vs JSON Serialization
本文以一组Entity vs Dataset的性能测试数据为基础,比较以Entity作为DTO和Dataset作为DTO的性能差异。测试可能不一定严密,但是一定程度上能够比较出优劣。希望能为您选择 .Net下不同的数据承载方式、序列化方式、DTO的选择,多一点参考。在本测试中,每个执行过程,对于Entity,我们将先用DataReader读出数据,使用索引将数据填充到Entity,序列化,再反序列化;对于Dataset,将先读取所有数据到Dataset,序列化,再反序列化,最后通过索引填充Dataset中的数据到Entity。也就是说,无论对Entity还是Dataset,我们都尽可能的取其最佳性能的执行方式,从而将性能瓶颈留在了序列化和反序列化方式上。您可以注意到,Dataset的序列化和反序列化性能是非常突出的,但是,我们基于Entity的自定义序列化方式的综合性能,超越了Dataset。
04/18补充:新增.NET JSON序列化对照。关于JSON的更多介绍请参见:http://www.json.org/。不过值得一提的是,官方提供的.Net实现写得那个烂得简直没话说。本测试使用Teddy修改由化后的.Net版本,性能是官方版本的30-40倍。
测试报告
就让我们先从一个测试报告开始,该测试读取Northwind数据库中Order Details Extended视图的所有数据,RepeatTime表示每个步骤(读数据、序列化、反序列化等等着每个步骤)被重复的次数。表中的时间数值单位为毫秒。
Ilungasoft Framework Data Access & Entities Serialization Performance Test
Title | Read Data Time | Serialize Time | Serialized Xml Size | DeserializeTime | Total Run Time |
---|---|---|---|---|---|
Entities Xml Serialize | 844 | 1766 | 702783 | 1750 | 4360 |
Entities Soap Serialize | 328 | 4094 | 3031007 | 6203 | 10625 |
Entities Custom Serialize | 359 | 594 | 598565 | 625 | 1578 |
DataSet Serialize | 515 | 532 | 586200 | 2047 | 3094 |
Entities JSON Serialize | 484 | 625 | 325298 | 641 | 1750 |
Title | Read Data Time | Serialize Time | Serialized Xml Size | Deserialize Time | Total Run Time |
---|---|---|---|---|---|
Entities Xml Serialize | 1093 | 2594 | 702783 | 3797 | 7484 |
Entities Soap Serialize | 734 | 8032 | 3031007 | 12000 | 20766 |
Entities Custom Serialize | 671 | 1188 | 598565 | 1234 | 3093 |
DataSet Serialize | 657 | 1062 | 586200 | 6563 | 8282 |
Entities JSON Serialize | 781 | 1094 | 325298 | 1093 | 2968 |
Title | Read Data Time | Serialize Time | Serialized Xml Size | Deserialize Time | Total Run Time |
---|---|---|---|---|---|
Entities Xml Serialize | 938 | 4140 | 702783 | 5547 | 10625 |
Entities Soap Serialize | 1094 | 12578 | 3031007 | 18891 | 32563 |
Entities Custom Serialize | 1140 | 1922 | 598565 | 2188 | 5250 |
DataSet Serialize | 1156 | 1438 | 586200 | 14984 | 17578 |
Entities JSON Serialize | 1141 | 1656 | 325298 | 1703 | 4500 |
Title | Read Data Time | Serialize Time | Serialized Xml Size | Deserialize Time | Total Run Time |
---|---|---|---|---|---|
Entities Xml Serialize | 1594 | 5047 | 702783 | 6968 | 13609 |
Entities Soap Serialize | 1391 | 16844 | 3031007 | 26093 | 44328 |
Entities Custom Serialize | 1875 | 2469 | 598565 | 2735 | 7079 |
DataSet Serialize | 1828 | 2093 | 586200 | 25969 | 29890 |
Entities JSON Serialize | 1719 | 2437 | 325298 | 2500 | 6656 |
解析
以上的测试中,除了Dataset Serialize是使用Dataset作为DTO来序列化和反序列化数据之外,其他的都是使用Entity方式来处理。Xml Serialize表示系统默认的XmlSerializer序列化方式,SoapSerilialize自然是系统默认的SoapFormatter序列化,CustomSerialize则是自定义的序列化方式(目前采用的是自定义序列化+XmlSerializer反序列化结合的方式)。Json Serialization为Teddy的Json优化版本。
测试代码
2using System.Data;
3using System.Collections;
4using System.Configuration;
5using System.Web;
6using System.Web.Security;
7using System.Web.UI;
8using System.Web.UI.WebControls;
9using System.Web.UI.WebControls.WebParts;
10using System.Web.UI.HtmlControls;
11using Ilungasoft.Framework.Common;
12using Ilungasoft.Framework.Data.Facade;
13
14public partial class _Default : System.Web.UI.Page
15{
16 protected void Page_Load(object sender, EventArgs e)
17 {
18 repeatTime = 10;
19 reportView1.DataSource = DoTest();
20
21 repeatTime = 20;
22 reportView2.DataSource = DoTest();
23
24 repeatTime = 30;
25 reportView3.DataSource = DoTest();
26
27 repeatTime = 40;
28 reportView4.DataSource = DoTest();
29
30 DataBind();
31 }
32
33 private TestReport[] DoTest()
34 {
35 TestReport[] testReports = new TestReport[5];
36 testReports[0] = DoTest1();
37 testReports[1] = DoTest2();
38 testReports[2] = DoTest3();
39 testReports[3] = DoTest4();
40 testReports[4] = DoTest5();
41
42 return testReports;
43 }
44
45 private long time;
46 private DataSet ds;
47 private Order_nbsp_Details_nbsp_Extended[] orderDetails;
48 public string xml;
49 private int repeatTime = 10;
50
51 private TestReport DoTest1()
52 {
53 TestReport testReport = new TestReport();
54 testReport.Title = "Entities Xml Serialize";
55
56 time = System.Environment.TickCount;
57 for (int i = 0; i < repeatTime; i++)
58 orderDetails = DefaultGateway.SelectAll<Order_nbsp_Details_nbsp_Extended>();
59 testReport.ReadDataTime = System.Environment.TickCount - time;
60
61 time = System.Environment.TickCount;
62 for (int i = 0; i < repeatTime; i++)
63 xml = SerializeHelper.SerializeArray(orderDetails);
64 testReport.SerializeTime = System.Environment.TickCount - time;
65 testReport.SerializedXmlSize = xml.Length;
66
67 time = System.Environment.TickCount;
68 for (int i = 0; i < repeatTime; i++)
69 orderDetails = SerializeHelper.Deserialize<Order_nbsp_Details_nbsp_Extended[]>(EntityFactory<Order_nbsp_Details_nbsp_Extended>.GetDynamicEntityType().MakeArrayType(), xml);
70 testReport.DeserializeTime = System.Environment.TickCount - time;
71
72 testReport.TotalRunTime = testReport.ReadDataTime + testReport.SerializeTime + testReport.DeserializeTime;
73
74 return testReport;
75 }
76
77 private TestReport DoTest2()
78 {
79 TestReport testReport = new TestReport();
80 testReport.Title = "Entities Soap Serialize";
81
82 time = System.Environment.TickCount;
83 for (int i = 0; i < repeatTime; i++)
84 orderDetails = DefaultGateway.SelectAll<Order_nbsp_Details_nbsp_Extended>();
85 testReport.ReadDataTime = System.Environment.TickCount - time;
86
87 time = System.Environment.TickCount;
88 for (int i = 0; i < repeatTime; i++)
89 xml = SerializeHelper.SoapSerialize(orderDetails);
90 testReport.SerializeTime = System.Environment.TickCount - time;
91 testReport.SerializedXmlSize = xml.Length;
92
93 time = System.Environment.TickCount;
94 for (int i = 0; i < repeatTime; i++)
95 orderDetails = SerializeHelper.SoapDeserialize<Order_nbsp_Details_nbsp_Extended[]>(xml);
96 testReport.DeserializeTime = System.Environment.TickCount - time;
97
98 testReport.TotalRunTime = testReport.ReadDataTime + testReport.SerializeTime + testReport.DeserializeTime;
99
100 return testReport;
101 }
102
103 private TestReport DoTest3()
104 {
105 TestReport testReport = new TestReport();
106 testReport.Title = "Entities Custom Serialize";
107
108 time = System.Environment.TickCount;
109 for (int i = 0; i < repeatTime; i++)
110 orderDetails = DefaultGateway.SelectAll<Order_nbsp_Details_nbsp_Extended>();
111 testReport.ReadDataTime = System.Environment.TickCount - time;
112
113 time = System.Environment.TickCount;
114 for (int i = 0; i < repeatTime; i++)
115 xml = SerializeHelper.SerializeEntityArray(orderDetails);
116 testReport.SerializeTime = System.Environment.TickCount - time;
117 testReport.SerializedXmlSize = xml.Length;
118
119 time = System.Environment.TickCount;
120 for (int i = 0; i < repeatTime; i++)
121 orderDetails = SerializeHelper.DeserializeEntityArray<Order_nbsp_Details_nbsp_Extended>(xml);
122 testReport.DeserializeTime = System.Environment.TickCount - time;
123
124 testReport.TotalRunTime = testReport.ReadDataTime + testReport.SerializeTime + testReport.DeserializeTime;
125
126 return testReport;
127 }
128
129 private TestReport DoTest4()
130 {
131 TestReport testReport = new TestReport();
132 testReport.Title = "DataSet Serialize";
133
134 time = System.Environment.TickCount;
135 for (int i = 0; i < repeatTime; i++)
136 ds = DefaultGateway.SelectDataSet("select * from [Order Details Extended]");
137 testReport.ReadDataTime = System.Environment.TickCount - time;
138
139 time = System.Environment.TickCount;
140 for (int i = 0; i < repeatTime; i++)
141 {
142 System.IO.MemoryStream ms = new System.IO.MemoryStream();
143 ds.WriteXml(ms);
144 xml = System.Text.UTF8Encoding.UTF8.GetString(ms.ToArray());
145 }
146 testReport.SerializeTime = System.Environment.TickCount - time;
147 testReport.SerializedXmlSize = xml.Length;
148
149 time = System.Environment.TickCount;
150 for (int i = 0; i < repeatTime; i++)
151 {
152 System.IO.MemoryStream ms = new System.IO.MemoryStream(System.Text.UTF8Encoding.UTF8.GetBytes(xml));
153 ds.ReadXml(ms);
154 orderDetails = DefaultGateway.CreateList<Order_nbsp_Details_nbsp_Extended>(ds.Tables[0]);
155 }
156 testReport.DeserializeTime = System.Environment.TickCount - time;
157
158 testReport.TotalRunTime = testReport.ReadDataTime + testReport.SerializeTime + testReport.DeserializeTime;
159
160 return testReport;
161 }
162
163 private TestReport DoTest5()
164 {
165 TestReport testReport = new TestReport();
166 testReport.Title = "Entities JSON Serialize";
167
168 time = System.Environment.TickCount;
169 for (int i = 0; i < repeatTime; i++)
170 orderDetails = DefaultGateway.SelectAll<Order_nbsp_Details_nbsp_Extended>();
171 testReport.ReadDataTime = System.Environment.TickCount - time;
172
173 time = System.Environment.TickCount;
174 for (int i = 0; i < repeatTime; i++)
175 xml = SerializeHelper.JsonSerializeEntityArray(orderDetails);
176 testReport.SerializeTime = System.Environment.TickCount - time;
177 testReport.SerializedXmlSize = xml.Length;
178
179 time = System.Environment.TickCount;
180 for (int i = 0; i < repeatTime; i++)
181 orderDetails = SerializeHelper.JsonDeserializeEntityArray<Order_nbsp_Details_nbsp_Extended>(xml);
182 testReport.DeserializeTime = System.Environment.TickCount - time;
183
184 testReport.TotalRunTime = testReport.ReadDataTime + testReport.SerializeTime + testReport.DeserializeTime;
185
186 return testReport;
187 }
188}
更多代码我就不列举了,请自行下载下面的源码。
下载
下载测试框架及源码 (本示例源码包含在新版本的Ilungasoft Framework v1.4.4的目录中的dist\Sample5)
小结
本测试示例的目的并不是要终结Dataset作为DTO,尽管Entity作为DTO的性能是可以超越Dataset的,但Teddy觉得,Dataset的性能确实是相当好的,作为一个通用的DTO还是非常适合的(尤其是可以和异构的其他.Net系统方便共享和传递数据)。
补遗:
后续的测试中还发现,无论是XmlSerializer的序列化方式都不够稳定,特别是反序列化时,尤其是对符合类型和数组的序列化。因此,对于大多数基于.Net的程序构架,Teddy还是推荐使用Dataset作为DTO,除非对性能要求非常高时在使用自定义的序列化方式。
4/19:
更新了CustomSerizlize算法,使得其执行时间仅为Dataset的1/2 - 1/10。数据量越大,自定义序列化的优势就越明显。
新增的JSON序列化性能基本和自定义Xml 序列化相当,当数据量较大时,要比自定义Xml序列化方式稍好一点点。
但是,请注意,JSON的序列化后的文本大小只有Dataset的一半,对于需要远程通信的程序来讲,JSON方式将极大的减少需要通信的数据量,因此,JSON方式带来的实际的性能提升可能会更大。