我们正在考虑用 JSON(WCF 或其他)调用替换(部分或许多)“经典”SOAP XML WCF 调用,因为直接在 Javascript 中的开销较低且易于使用。目前,我们刚刚为我们的 Web 服务添加了一个额外的 Json 端点,并为一些操作添加了 WebInvoke 属性并对其进行了测试。一切正常,使用 C# .Net 客户端或 Javascript 客户端。到目前为止一切顺利。
然而,在 C# .Net 中将大的 JSON 字符串反序列化为对象似乎比反序列化 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)
我在 Release模式下运行应用程序。客户端和服务器都在同一台机器上。如您所见,与 WCF SOAP 使用的 XML 到对象的映射相比,使用 JSON 对许多(相同类型的)对象进行反序列化需要更多时间。该死,仅反序列化就比使用 SOAP 调用整个 Web 服务花费的时间更多。
对此有解释吗? XML(或 WCF SOAP 实现)是否在这方面提供了很大的优势,或者我可以在客户端更改任何东西(我宁愿不更改服务,但更改客户端 DTO 是可以接受的)尝试提高性能?感觉就像我已经在 JSON.net 端选择了一些设置,它们应该比默认设置更快,不是吗?这里的瓶颈似乎是什么?
最佳答案
我花了更多时间阅读有关 JSON.NET 内部结构的信息,我的结论是,速度缓慢主要是由 反射 引起的。
在 JSON.NET 网站上我发现了一些 nice performance tips ,并且我尝试了几乎所有东西(JObject.Parse、自定义转换器等),但我无法挤出任何显着的性能改进。然后我阅读了整个网站上最重要的注释:
If performance is important and you don't mind more code to get it then this is your best choice. Read more about using JsonReader/JsonWriter here
所以我听取了建议,并实现了一个基本版本的 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 中获得的最佳性能。
只是这个有限的代码比我有 500 个房间的盒子上的 Deserialize
版本快 12 倍,但当然映射还没有完成。但是,我很确定在最坏的情况下它至少会比反序列化快 5 倍。
查看此链接以获取有关 JsonReader 及其使用方法的更多信息:
http://james.newtonking.com/json/help/html/ReadingWritingJSON.htm
关于c# - 如何提高 .Net 中的 JSON 反序列化速度? (JSON.net 还是其他?),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26380184/