it-swarm.com.de

Wie kann man die JSON-Deserialisierungsgeschwindigkeit in .NET verbessern? (JSON.net oder andere?)

Wir erwägen, (einige oder viele) 'classic' SOAP XML-WCF-Aufrufe durch JSON-Aufrufe (WCF oder andere) zu ersetzen, da der Overhead geringer ist und die Verwendung direkt in Javascript erleichtert wird. Wir haben vor kurzem unserem Web-Service einen zusätzlichen Json-Endpunkt hinzugefügt und einige Vorgänge mit WebInvoke-Attributen versehen und getestet. Alles funktioniert gut, mit C # .NET-Clients oder Javascript-Clients. So weit, ist es gut.

Es scheint jedoch, als ob eine Deserialisierung großer JSON-Strings zu Objekten in C # .Net wesentlich langsamer ist als eine Deserialisierung SOAP XML. Beide verwenden DataContract- und DataMember-Attribute (exakt dasselbe DTO). Meine Frage ist: Wird das erwartet? Können wir irgendetwas tun, um diese Leistung zu optimieren? Oder sollten wir JSON nur für kleinere Anforderungen in Betracht ziehen, bei denen wir Leistungsverbesserungen bemerken.

Im Moment haben wir uns für diesen Test für JSON.net entschieden, und obwohl es in diesem Testfall nicht angezeigt wird, sollte es schneller als die .Net-JSON-Serialisierung sein. Irgendwie funktioniert die ServiceStack-Deserialisierung überhaupt nicht (kein Fehler, gibt für IList null zurück).

Für den Test führen wir einen Serviceabruf durch, um eine Liste der Räume zu sammeln. Es gibt eine GetRoomListResponse zurück. Wenn Sie 5 Dummy-Räume zurückgeben, sieht der JSON folgendermaßen aus:

{"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}}]}

Die Antwort und die DTOs sehen so aus:

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

Dann lautet unser Testcode wie folgt:

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

Jetzt haben wir diese Anwendung mit 50, 500 und 5000 Zimmern ausgeführt. Die Objekte sind nicht sehr komplex.

Das sind die Ergebnisse; Zeiten sind in ms:

50 Zimmer:

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 Zimmer:

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 Zimmer:

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)

Ich führe die Anwendung im Freigabemodus aus. Sowohl Client als auch Server auf demselben Computer. Wie Sie sehen, dauert die Deserialisierung vieler (desselben Typs) mit JSON viel mehr Zeit als die Zuordnung von XML zu Objekten, die von WCF SOAP verwendet wird. Verdammt, die Deserialisierung alleine dauert länger als der gesamte Web-Service-Aufruf mit SOAP.

Gibt es dafür eine Erklärung? Bietet XML (oder die WCF SOAP Implementierung) einen großen Vorteil in diesem Bereich oder gibt es irgendwelche Dinge, die ich auf der Clientseite ändern kann (ich möchte den Dienst nicht ändern, sondern die DTOs auf der Clientseite ändern) ist akzeptabel) zu versuchen, die Leistung zu verbessern? Es fühlt sich an, als ob ich bereits einige Einstellungen auf der JSON.net-Seite ausgewählt habe, die es schneller machen sollten als die Standardeinstellungen. Was scheint hier der Engpass zu sein?

37
Colin B

Ich habe ein wenig mehr Zeit damit verbracht, über JSON.NET-Interna zu lesen, und meine Schlussfolgerung ist, dass die Langsamkeit hauptsächlich durch Reflektion verursacht wird. 

Auf der JSON.NET-Seite habe ich einige Nice Performance Tips gefunden, und ich habe so ziemlich alles versucht (JObject.Parse, Custom Converters usw.), aber ich konnte keine signifikanten Leistungsverbesserungen herausholen. Dann lese ich den wichtigsten Hinweis auf der gesamten Website:

Wenn Leistung wichtig ist und Sie nicht mehr Code benötigen, um ihn zu erhalten, ist dies die beste Wahl. Weitere Informationen zur Verwendung von JsonReader/JsonWriter finden Sie hier

Also hörte ich den Rat und implementierte eine Basisversion eines JsonReader, um den String effizient lesen zu können:

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

Ich denke, die Übung ist klar und ohne Zweifel ist dies die beste Leistung, die Sie mit JSON.NET erzielen können.

Nur dieser eingeschränkte Code ist 12x schneller als die Deserialize-Version in meiner Box mit 500 Zimmern, aber das Mapping ist natürlich nicht abgeschlossen. Ich bin mir jedoch ziemlich sicher, dass es im schlimmsten Fall mindestens fünfmal schneller ist als die Deserialisierung.

Weitere Informationen zum JsonReader und seiner Verwendung finden Sie unter diesem Link:

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

32
Faris Zacina

Ich habe jetzt die Vorschläge von The ZenCoder und von mythz verwendet und weitere Tests durchgeführt. Ich habe auch einen Fehler in meinem ersten Test-Setup festgestellt, weil ich die Test-App während der Erstellung des Tools im Freigabemodus von Visual Studio aus gestartet habe, was immer noch etwas Debug-Overhead hinzufügte, und dies machte das JSON.Net wesentlich stärker Verglichen mit der SOAP XML-Seite auf meinem PC, war der Unterschied in der Praxis der ersten Testergebnisse bereits etwas geringer.

Im Folgenden sind die Ergebnisse des Sammelns von 5000/50000 Räumen vom Server (localhost) aufgeführt, einschließlich der Zuordnung zu Modellen.

5000 Zimmer:

----- 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 Zimmer:

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

Legende:

  • JSON.Net (Reflektion) -> JsonConvert.DeserializeObject (gleicher JSON.Net-Code wie oben)
  • ServiceStack (Reflektion) -> JsonSerializer.DeserializeFromString
  • SOAP Xml (manuelle Zuordnung) -> Same SOAP Clientaufruf wie oben mit hinzugefügter Zuordnung von DTOs zu Modellen
  • JSON.Net (manuelle Zuordnung) -> Zuordnen von JSON zu Modellen direkt unter Verwendung von Code basierend auf dem Code von The ZenCoder oben, erweitert um die Zuordnung für die gesamte Anforderung (auch Räume und Standorte).

  • ServiceStack (manuelle Zuordnung) -> Siehe den folgenden Code (basierend auf dem Beispiel: 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"),
                    }),
                }),
            });
    

Anmerkungen/persönliche Schlussfolgerungen:

  • Selbst die reflexbasierte Deserialisierung ist nicht viel langsamer als SOAP Generierung von XML-Objekten im tatsächlichen Freigabemodus (oops).
  • Manuelles Mapping in JSON.Net ist schneller als das automatische Mapping und ist in der Geschwindigkeit sehr vergleichbar mit SOAP Xml-Mapping-Leistung. Es bietet eine Menge Freiheit, was besonders bei Modellen und DTOs von Vorteil ist
  • Das manuelle ServiceStack-Mapping ist tatsächlich langsamer als das auf Reflektion basierende Mapping. Ich vermute, das liegt daran, dass es sich um ein manuelles Mapping auf höherer Ebene als auf der JSON.Net-Seite handelt, da dort scheinbar bereits eine Objektgenerierung stattgefunden hat. Vielleicht gibt es auch auf der ServiceStack-Seite untergeordnete Alternativen?
  • All dies wurde mit Server/Client-Code auf demselben Computer ausgeführt. Ich bin mir sicher, dass die JSON-Lösungen in separaten Client/Server-Produktionsumgebungen SOAP XML schlagen sollten, da sehr viel kleinere Nachrichten über das Netzwerk gesendet werden müssen
  • In dieser Situation scheint die automatische Zuordnung von JSON.Net etwas schneller als die von ServiceStack zu sein, um große Antworten zu erhalten.
4
Colin B
var receivedObject = JsonConvert.DeserializeObject<dynamic>(content);

arbeitet für mich dann viel schneller:

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

und das geht noch schneller:

dynamic receivedObject = JObject.Parse(content); // The same goes for JArray.Parse()
0
JedatKinports

Ich füge hier 2 weitere Punkte hinzu, die mir helfen, die Leistung meiner IoT-Anwendung zu verbessern. Ich erhielt täglich Millionen von JSON-Nachrichten.

  1. Änderungen in der ContractResolver -Instanz

Alter Code

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

Neuer Code

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

Alter Code

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

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

Neuer Code

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

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

Um die Leistungsvorteile zu überprüfen, habe ich benchmarkdotnet verwendet, um den Unterschied zu erkennen. Überprüfen Sie auch diese Verknüpfung .

0
Pankaj Rawat