How to improve JSON deserialization speed in .Net? (JSON.net or other?)

前端 未结 4 1444
旧巷少年郎
旧巷少年郎 2020-12-12 21:11

We\'re considering replacing (some or many) \'classic\' SOAP XML WCF calls by JSON (WCF or other) calls, because of the lower overhead and ease of use directly in Javascript

相关标签:
4条回答
  • 2020-12-12 21:48
    var receivedObject = JsonConvert.DeserializeObject<dynamic>(content);
    

    works much faster for me then:

    var receivedObject = JsonConvert.DeserializeObject<Product>(content);
    

    and this is even faster:

    dynamic receivedObject = JObject.Parse(content); // The same goes for JArray.Parse()
    
    0 讨论(0)
  • 2020-12-12 21:55

    I'm adding 2 more points here, which help me to improve my IoT application performance. I was receiving millions of JSON messages every day.

    1. Changes in ContractResolver instance

    Old code

    return JsonConvert.SerializeObject(this, Formatting.Indented,
                              new JsonSerializerSettings
                              {
                                  ContractResolver = new CamelCasePropertyNamesContractResolver()
                              });
    

    New Code

    Not creating contract resolver instance on every call, Instead using a single instance

    return JsonConvert.SerializeObject(this, Formatting.Indented,
                              new JsonSerializerSettings
                              {
                                  ContractResolver = AppConfiguration.CamelCaseResolver
                              });
    
    1. Avoid creating JObject

    Old Code

    JObject eventObj = JObject.Parse(jsonMessage);
    eventObj.Add("AssetType", assetType); //modify object
    
    JObject eventObj2 = JObject.Parse(jsonMessage);
    eventObj.Add("id", id); //modify object
    

    NewCode

    JObject eventObj = JObject.Parse(jsonMessage);
    eventObj.Add("AssetType", assetType); //modify object
    
    JObject eventObj2 = (JObject)eventObj.DeepClone();
    eventObj.Add("id", id); //modify object
    

    To check performance benefits, I used benchmarkdotnet to see the difference. check this link as well.

    0 讨论(0)
  • 2020-12-12 22:01

    I have spent a little bit more time reading about JSON.NET internals, and my conclusion is that the slowness is caused mostly by reflection.

    On the JSON.NET site i have found some nice performance tips, and i tried pretty much everything (JObject.Parse, Custom Converters etc.) but i couldn't squeeze out any significant performance improvement. Then i read the most important note on the whole site:

    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

    So i listened to the advice and i implemented a basic version of a JsonReader to read the string efficiently:

    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
        }
    }
    

    I think the exercise is clear, and without doubt this is the best performance you can get out of JSON.NET.

    Just this limited code is 12x faster than the Deserialize version on my box with 500 rooms, but of course the mapping is not completed. However, i am pretty sure it will be at least 5x faster than deserialization in the worst-case.

    Check out this link for more info about the JsonReader and how to use it:

    http://james.newtonking.com/json/help/html/ReadingWritingJSON.htm

    0 讨论(0)
  • 2020-12-12 22:04

    I have now used the suggestions by both The ZenCoder and mythz and I have done more testing. I noticed an error in my first test setup as well, because while I built the tool in Release mode, I still started the test app from Visual Studio, which still added some debug overhead and this made a much bigger difference on the JSON.Net side compared to the SOAP XML side on my PC, so the difference in practice of the initial test results was quite a bit smaller already.

    Either way, below are the results of collecting 5000 / 50000 rooms from the server (localhost), including mapping them to models.

    5000 rooms:

    ----- Test results for JSON.Net (reflection) -----
    
    GetRoomList (5000): 107
    GetRoomList (5000): 60
    GetRoomList (5000): 65
    GetRoomList (5000): 62
    GetRoomList (5000): 63
    
    ----- Test results for ServiceStack (reflection) -----
    
    GetRoomList (5000): 111
    GetRoomList (5000): 62
    GetRoomList (5000): 62
    GetRoomList (5000): 60
    GetRoomList (5000): 62
    
    ----- Test results for SOAP Xml (manual mapping) -----
    
    GetRoomList (5000): 101
    GetRoomList (5000): 47
    GetRoomList (5000): 51
    GetRoomList (5000): 49
    GetRoomList (5000): 51
    
    ----- Test results for Json.Net (manual mapping) -----
    
    GetRoomList (5000): 58
    GetRoomList (5000): 47
    GetRoomList (5000): 51
    GetRoomList (5000): 49
    GetRoomList (5000): 47
    
    ----- Test results for ServiceStack (manual mapping) -----
    
    GetRoomList (5000): 91
    GetRoomList (5000): 79
    GetRoomList (5000): 64
    GetRoomList (5000): 66
    GetRoomList (5000): 77
    

    50000 rooms:

    ----- Test results for JSON.Net (reflection) -----
    
    GetRoomList (50000): 651
    GetRoomList (50000): 628
    GetRoomList (50000): 642
    GetRoomList (50000): 625
    GetRoomList (50000): 628
    
    ----- Test results for ServiceStack (reflection) -----
    
    GetRoomList (50000): 754
    GetRoomList (50000): 674
    GetRoomList (50000): 658
    GetRoomList (50000): 657
    GetRoomList (50000): 654
    
    ----- Test results for SOAP Xml (manual mapping) -----
    
    GetRoomList (50000): 567
    GetRoomList (50000): 556
    GetRoomList (50000): 561
    GetRoomList (50000): 501
    GetRoomList (50000): 543
    
    ----- Test results for Json.Net (manual mapping) -----
    
    GetRoomList (50000): 575
    GetRoomList (50000): 569
    GetRoomList (50000): 515
    GetRoomList (50000): 539
    GetRoomList (50000): 526
    
    ----- Test results for ServiceStack (manual mapping) -----
    
    GetRoomList (50000): 850
    GetRoomList (50000): 796
    GetRoomList (50000): 784
    GetRoomList (50000): 805
    GetRoomList (50000): 768
    

    Legend:

    • JSON.Net (reflection) -> JsonConvert.DeserializeObject (same JSON.Net code as above)
    • ServiceStack (reflection) -> JsonSerializer.DeserializeFromString
    • SOAP Xml (manual mapping) -> Same SOAP client call as above with added mapping from DTO's to models
    • JSON.Net (manual mapping) -> Mapping JSON to models directly using code based on The ZenCoder's code above, expanded to include mapping for the entire request (rooms and locations as well)

    • ServiceStack (manual mapping) -> See the below code (based on example: https://github.com/ServiceStack/ServiceStack.Text/blob/master/tests/ServiceStack.Text.Tests/UseCases/CentroidTests.cs)

              var response = JsonObject.Parse(responseData).ConvertTo(x => new GetRoomListResponse()
              {
                  Acknowledge = (AcknowledgeType)x.Get<int>("Acknowledge"),
                  Code = x.Get<int>("Code"),
                  Exception = x.Get("Exception"),
                  Message = x.Get("Message"),
                  RoomList = x.ArrayObjects("RoomList").ConvertAll<RoomModel>(y => new RoomModel()
                  {
                      Id = y.Get<Guid>("Id"),
                      Description = y.Get("Description"),
                      Location = y.Object("Location").ConvertTo<LocationModel>(z => new LocationModel()
                      {
                          Id = z.Get<Guid>("Id"),
                          Code = z.Get("Code"),
                          Description = z.Get("Description"),
                          Number = z.Get<int>("Number"),
                      }),
                  }),
              });
      

    Notes / personal conclusions:

    • Even reflection based deserialization is not that much slower than SOAP XML object generation in actual release mode (oops)
    • Manual mapping in JSON.Net is faster than the auto mapping and it is very comparable in speed to SOAP Xml mapping performance and it offers a lot of freedom, which is great, especially when models and DTO's differ in places
    • ServiceStack manual mapping is actually slower than their full reflection based mapping. I'm guessing this is because it's a higher level manual mapping than on the JSON.Net side, because some object generation seems to have already occurred there. Perhaps there are lower level alternatives on the ServiceStack side as well?
    • All this was done with server / client code running on same machine. In separate client / server production environments, I'm sure the JSON solutions should beat SOAP XML because of much smaller messages that need to be sent over the network
    • In this situation, JSON.Net auto mapping appears to be a tad faster than ServiceStack's for big responses.
    0 讨论(0)
提交回复
热议问题