我们正在考虑用JSON(WCF或其他)调用替换(一些或许多)“经典” SOAP XML WCF调用,因为其开销较低且易于直接在Javascript中使用。现在,我们刚刚在Web服务中添加了一个额外的Json端点,并在某些操作中添加了WebInvoke属性并对其进行了测试。使用C#.Net客户端或Javascript客户端,一切正常。到目前为止,一切都很好。
但是,似乎将大JSON字符串反序列化为C#.Net中的对象比反序列化SOAP XML慢得多。两者都使用DataContract和DataMember属性(完全相同的DTO)。我的问题是:这是预期的吗?我们可以做些什么来优化此性能?或者,我们应该只对较小的请求考虑JSON,但确实注意到性能有所提高。
到目前为止,我们已经选择JSON.net进行此测试,即使在此测试用例中未显示它,也应该比.Net JSON序列化要快。不知何故,ServiceStack反序列化根本不起作用(无错误,为IList返回null)。
为了进行测试,我们进行了服务呼叫以收集房间列表。它返回一个GetRoomListResponse,并且在返回5个虚拟房间的情况下,JSON如下所示:
{"Acknowledge":1,"Code":0,"Message":null,"ValidateErrors":null,"Exception":null,"RoomList":[{"Description":"DummyRoom","Id":"205305e6-9f7b-4a6a-a1de-c5933a45cac0","Location":{"Code":"123","Description":"Location 123","Id":"4268dd65-100d-47c8-a7fe-ea8bf26a7282","Number":5}},{"Description":"DummyRoom","Id":"aad737f7-0caa-4574-9ca5-f39964d50f41","Location":{"Code":"123","Description":"Location 123","Id":"b0325ff4-c169-4b56-bc89-166d4c6d9eeb","Number":5}},{"Description":"DummyRoom","Id":"c8caef4b-e708-48b3-948f-7a5cdb6979ef","Location":{"Code":"123","Description":"Location 123","Id":"11b3f513-d17a-4a00-aebb-4d92ce3f9ae8","Number":5}},{"Description":"DummyRoom","Id":"71376c49-ec41-4b12-b5b9-afff7da882c8","Location":{"Code":"123","Description":"Location 123","Id":"1a188f13-3be6-4bde-96a0-ef5e0ae4e437","Number":5}},{"Description":"DummyRoom","Id":"b947a594-209e-4195-a2c8-86f20eb883c4","Location":{"Code":"123","Description":"Location 123","Id":"053e9969-d0ed-4623-8a84-d32499b5a8a8","Number":5}}]}
响应和DTO如下所示:
[DataContract(Namespace = "bla")] public class GetRoomListResponse { [DataMember] public IList<Room> RoomList; [DataMember] public string Exception; [DataMember] public AcknowledgeType Acknowledge = AcknowledgeType.Success; [DataMember] public string Message; [DataMember] public int Code; [DataMember] public IList<string> ValidateErrors; } [DataContract(Name = "Location", Namespace = "bla")] public class Location { [DataMember] public Guid Id { get; set; } [DataMember] public int Number { get; set; } [DataMember] public string Code { get; set; } [DataMember] public string Description { get; set; } } [DataContract(Name = "Room", Namespace = "bla")] public class Room { [DataMember] public Guid Id { get; set; } [DataMember] public string Description { get; set; } [DataMember] public Location Location { get; set; } }
然后我们的测试代码如下:
static void Main(string[] args) { SoapLogin(); Console.WriteLine(); SoapGetRoomList(); SoapGetRoomList(); SoapGetRoomList(); SoapGetRoomList(); SoapGetRoomList(); SoapGetRoomList(); SoapGetRoomList(); Console.WriteLine(); JsonDotNetGetRoomList(); JsonDotNetGetRoomList(); JsonDotNetGetRoomList(); JsonDotNetGetRoomList(); JsonDotNetGetRoomList(); JsonDotNetGetRoomList(); JsonDotNetGetRoomList(); Console.ReadLine(); } private static void SoapGetRoomList() { var request = new TestServiceReference.GetRoomListRequest() { Token = Token, }; Stopwatch sw = Stopwatch.StartNew(); using (var client = new TestServiceReference.WARPServiceClient()) { TestServiceReference.GetRoomListResponse response = client.GetRoomList(request); } sw.Stop(); Console.WriteLine("SOAP GetRoomList: " + sw.ElapsedMilliseconds); } private static void JsonDotNetGetRoomList() { var request = new GetRoomListRequest() { Token = Token, }; Stopwatch sw = Stopwatch.StartNew(); long deserializationMillis; using (WebClient client = new WebClient()) { client.Headers["Content-type"] = "application/json"; client.Encoding = Encoding.UTF8; string requestData = JsonConvert.SerializeObject(request, JsonSerializerSettings); var responseData = client.UploadString(GetRoomListAddress, requestData); Stopwatch sw2 = Stopwatch.StartNew(); var response = JsonConvert.DeserializeObject<GetRoomListResponse>(responseData, JsonSerializerSettings); sw2.Stop(); deserializationMillis = sw2.ElapsedMilliseconds; } sw.Stop(); Console.WriteLine("JSON.Net GetRoomList: " + sw.ElapsedMilliseconds + " (deserialization time: " + deserializationMillis + ")"); } private static JsonSerializerSettings JsonSerializerSettings { get { var serializerSettings = new JsonSerializerSettings(); serializerSettings.CheckAdditionalContent = false; serializerSettings.ConstructorHandling = ConstructorHandling.Default; serializerSettings.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat; serializerSettings.DefaultValueHandling = DefaultValueHandling.Ignore; serializerSettings.NullValueHandling = NullValueHandling.Ignore; serializerSettings.ObjectCreationHandling = ObjectCreationHandling.Replace; serializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.None; serializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Error; return serializerSettings; } }
现在,我们以返回50、500和5000个房间的方式运行了该应用程序。对象不是很复杂。
这些是结果;时间以毫秒为单位:
50间客房:
SOAP GetRoomList: 37 SOAP GetRoomList: 5 SOAP GetRoomList: 4 SOAP GetRoomList: 4 SOAP GetRoomList: 9 SOAP GetRoomList: 5 SOAP GetRoomList: 5 JSON.Net GetRoomList: 289 (deserialization time: 91) JSON.Net GetRoomList: 3 (deserialization time: 0) JSON.Net GetRoomList: 2 (deserialization time: 0) JSON.Net GetRoomList: 2 (deserialization time: 0) JSON.Net GetRoomList: 2 (deserialization time: 0) JSON.Net GetRoomList: 2 (deserialization time: 0) JSON.Net GetRoomList: 2 (deserialization time: 0)
500间客房:
SOAP GetRoomList: 47 SOAP GetRoomList: 9 SOAP GetRoomList: 8 SOAP GetRoomList: 8 SOAP GetRoomList: 8 SOAP GetRoomList: 8 SOAP GetRoomList: 8 JSON.Net GetRoomList: 301 (deserialization time: 100) JSON.Net GetRoomList: 12 (deserialization time: 8) JSON.Net GetRoomList: 12 (deserialization time: 8) JSON.Net GetRoomList: 12 (deserialization time: 8) JSON.Net GetRoomList: 11 (deserialization time: 8) JSON.Net GetRoomList: 11 (deserialization time: 8) JSON.Net GetRoomList: 15 (deserialization time: 12)
5000间客房:
SOAP GetRoomList: 93 SOAP GetRoomList: 51 SOAP GetRoomList: 58 SOAP GetRoomList: 60 SOAP GetRoomList: 53 SOAP GetRoomList: 53 SOAP GetRoomList: 51 JSON.Net GetRoomList: 405 (deserialization time: 175) JSON.Net GetRoomList: 107 (deserialization time: 79) JSON.Net GetRoomList: 108 (deserialization time: 82) JSON.Net GetRoomList: 112 (deserialization time: 85) JSON.Net GetRoomList: 105 (deserialization time: 79) JSON.Net GetRoomList: 111 (deserialization time: 81) JSON.Net GetRoomList: 110 (deserialization time: 82)
我正在发布模式下运行该应用程序。客户端和服务器都在同一台计算机上。如您所见,与WCF SOAP使用的XML到对象的映射相比,使用JSON对许多(相同类型的)对象进行反序列化要花费更多的时间。糟糕的是,反序列化比使用SOAP进行整个Web服务调用要花费更多的时间。
对此有解释吗?XML(或WCF SOAP实现)在这方面是否提供了很大的优势,或者我可以在客户端进行任何更改(我宁愿不更改服务,但可以更改客户端DTO可以接受)以尝试提高绩效?感觉好像我已经在JSON.net端选择了一些设置,这些设置应该使其比默认设置更快,不是吗?这里的瓶颈似乎是什么?
我花了更多时间阅读有关JSON.NET内部的信息,我的结论是,速度缓慢主要是由 反射 引起的。
在JSON.NET站点上,我发现了一些不错的性能提示,并且尝试了几乎所有方法(JObject.Parse,自定义转换器等),但是我无法实现任何显着的性能改进。然后,我阅读了整个网站上最重要的说明:
如果性能很重要,并且您不介意获得更多代码,那么这是您的最佳选择。在此处阅读有关使用JsonReader / JsonWriter的更多信息
因此,我听了建议,并实现了JsonReader的基本版本以有效地读取字符串:
var reader = new JsonTextReader(new StringReader(jsonString)); var response = new GetRoomListResponse(); var currentProperty = string.Empty; while (reader.Read()) { if (reader.Value != null) { if (reader.TokenType == JsonToken.PropertyName) currentProperty = reader.Value.ToString(); if (reader.TokenType == JsonToken.Integer && currentProperty == "Acknowledge") response.Acknowledge = (AcknowledgeType)Int32.Parse(reader.Value.ToString()); if (reader.TokenType == JsonToken.Integer && currentProperty == "Code") response.Code = Int32.Parse(reader.Value.ToString()); if (reader.TokenType == JsonToken.String && currentProperty == "Message") response.Message = reader.Value.ToString(); if (reader.TokenType == JsonToken.String && currentProperty == "Exception") response.Exception = reader.Value.ToString(); // Process Rooms and other stuff } else { // Process tracking the current nested element } }
我认为练习很清楚, 毫无疑问,这是您可以从JSON.NET中获得的最佳性能 。
仅此有限的代码比Deserialize我有500个房间的盒子上的版本快12倍,但是映射当然没有完成。但是,我很确定在最坏的情况下它至少比反序列化快5倍。
Deserialize
查看此链接,以获取有关JsonReader以及如何使用它的更多信息:
http://james.newtonking.com/json/help/html/ReadingWritingJSON.htm