c# - 如何提高 .Net 中的 JSON 反序列化速度? (JSON.net 还是其他?)

标签 c# .net json performance json.net

我们正在考虑用 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 是可以接受的)尝试提高性能?感觉就像我已经在 J​​SON.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/

相关文章:

c# - 如何创建应用程序的 WPF 交互式导览

c# - 在 c#/xaml metro 风格应用程序的 MediaCapture 示例中获取 "Exception from HRESULT: 0xC00DABE0"

c# - 如何使用 .NET 客户端以编程方式删除托管在 couchbase 中的数据库存储桶中的所有数据

c# - 允许每个实例一个代理的最佳 WebBrowser 控件是什么?

javascript - 为什么 JSON.parse 对引号如此挑剔?

javascript - 如何区分字符串和 JSON

c# - 禁用 MaskedTextBox 声音

c# - 可选组的正则表达式

c# - 尝试在项目上启用多语言应用程序工具包时,没有任何反应

javascript - 按值对 JavaScript 对象排序