Zastanawiamy się nad zastąpieniem (niektórych lub wielu) "klasycznych" wywołań WCF SOAP XML WCF przez wywołania JSON (WCF lub inne) ze względu na niższe obciążenie i łatwość użycia bezpośrednio w JavaScript. Na razie dodaliśmy dodatkowy punkt końcowy Json do naszej usługi internetowej i dodaliśmy atrybuty WebInvoke do niektórych operacji i przetestowaliśmy je. Wszystko działa dobrze, używając klientów C# .Net lub klientów Javascript. Jak na razie dobrze.Jak poprawić szybkość deserializacji JSON w .Net? (JSON.net lub inny?)
Jednak wygląda na to, że następuje deserializacja dużych łańcuchów JSON do obiektów w języku C# .Net jest znacznie wolniejsza niż deserializacja protokołu SOAP XML. Oba używają atrybutów DataContract i DataMember (dokładnie to samo DTO). Moje pytanie brzmi: czy to jest oczekiwane? Czy jest coś, co możemy zrobić, aby zoptymalizować tę wydajność? Czy powinniśmy rozważyć JSON tylko w przypadku mniejszych wniosków, w których zauważamy poprawę wydajności.
Na razie wybraliśmy JSON.net dla tego testu i mimo że nie jest on wyświetlany w tym przypadku testowym, powinien być szybszy niż serializacja .NET JSON. W jakiś sposób deserializacja ServiceStack w ogóle nie działa (brak błędu, zwraca wartość pustą dla IList).
Do testu wykonujemy wywołanie serwisowe w celu zebrania listy pokoi. Zwraca GetRoomListResponse aw przypadku zwrotu 5 pokoi obojętne, JSON wygląda następująco:
{"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}}]}
odpowiedź i wygląd dto jest tak:
[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; }
}
Wtedy nasz kod test jest w następujący sposób:
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;
}
}
Teraz uruchomiliśmy tę aplikację, zwracając 50, 500 i 5000 pokoi. Obiekty nie są zbyt skomplikowane.
Oto wyniki; czasy w ms:
50 pokoje:
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 Pokoje:
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 Pokoje:
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)
biegnę aplikacji w trybie uwolnienia. Zarówno klient, jak i serwer na tej samej maszynie. Jak widać, deserializacja wielu obiektów tego samego typu zajmuje dużo więcej czasu przy użyciu JSON, niż w przypadku XML w celu mapowania obiektów, które korzysta z WCF SOAP. Piekło, sama deserializacja zajmuje więcej czasu niż całe wywołanie usługi sieciowej za pomocą SOAP.
Czy istnieje wyjaśnienie tego? Czy XML (lub implementacja SOAP WCF) oferuje dużą przewagę w tym obszarze, czy są rzeczy, które mogę zmienić po stronie klienta (wolę nie zmieniać usługi, ale zmiana DTO po stronie klienta jest akceptowalna), aby spróbować Polepsz wykonanie? Czuję, że już wybrałem niektóre ustawienia strony JSON.net, które powinny sprawić, że będzie to szybsze niż ustawienia domyślne, nie? Co wydaje się tutaj wąskim gardłem?
Powinienem może być sformułowany tytuł lepiej jak teraz może wydawać się duplikat, ale drugi temat to bardzo ogólne pytanie i faktycznie wypróbowałem dwa z najbardziej wydajnych (ServiceStack nie działa). Interesuje mnie głównie to, co powoduje dużą różnicę w deserializacji w porównaniu z XML SOAP i co najlepiej działa, by poprawić wydajność w mojej sytuacji. –
@ColinB domyślnie ServiceStack Serializer serializuje tylko ** właściwości publiczne **, gdzie jako twój DTO 'GetRoomListResponse' używa publicznych ** pól **. Możesz uzyskać ServiceStack do serializacji pól publicznych za pomocą: 'JsConfig.IncludePublicFields = true'. – mythz
@ Mythz dzięki, to załatwiło sprawę. Wydaje się, że jest nieco szybszy niż JSON.Net, wydaje się, że ich reputacja jest najszybsza, jest tutaj prawdziwa (choć nieco wolniejsza niż XML SOAP wciąż dla dużych list). Podobnie jak JSON.Net wygląda na to, że niestandardowa deserializacja (wyjaśniona przez TheZencoder) jest również możliwa dzięki ServiceStack.Text, więc gdy będę mieć czas, poszerzę swój pakiet testowy i uwzględnię niestandardowe deserializowane wyniki zarówno dla ServiceStack, jak i JSON.Net. –