.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优化版本。
测试代码
using System;2
using System.Data;3
using System.Collections;4
using System.Configuration;5
using System.Web;6
using System.Web.Security;7
using System.Web.UI;8
using System.Web.UI.WebControls;9
using System.Web.UI.WebControls.WebParts;10
using System.Web.UI.HtmlControls;11
using Ilungasoft.Framework.Common;12
using Ilungasoft.Framework.Data.Facade;13

14
public 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方式带来的实际的性能提升可能会更大。



浙公网安备 33010602011771号