web-dev-qa-db-ja.com

.NetでJSONのデシリアライズ速度を改善するにはどうすればよいですか? (JSON.netまたはその他?)

オーバーヘッドが低く、JavaScriptで直接使用できるため、(一部または多数の) 'クラシック' SOAP XML WCF呼び出しをJSON(WCFまたはその他)呼び出しに置き換えることを検討しています。 、JsonエンドポイントをWebサービスに追加し、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)

リリースモードでアプリケーションを実行しています。同じマシン上のクライアントとサーバーの両方。ご覧のとおり、多くの(同じタイプの)オブジェクトの逆シリアル化は、WCF SOAPが使用するXMLからオブジェクトへのマッピングよりも、JSONではるかに時間がかかります。 SOAPを使用したWebサービス呼び出し全体。

これについての説明はありますか? XML(またはWCF SOAP実装)はこの分野で大きな利点を提供しますか、またはクライアント側で変更できることはありますか(サービスを変更したくないのですが、クライアント側のDTOは許容可能です)パフォーマンスを改善しようとしていますか?デフォルト設定よりも速くする必要があるJSON.net側の設定をすでに選択しているように感じますが、ここでボトルネックは何ですか?

43
Colin B

私はもう少しJSON.NETの内部について読むことに時間を費やしました、そして、私の結論は、遅さが主にreflectionによって引き起こされるということです。

JSON.NETサイトでいくつかの 素敵なパフォーマンスのヒント を見つけ、ほとんどすべて(JObject.Parse、カスタムコンバーターなど)を試しましたが、パフォーマンスの大幅な改善を絞り出すことはできませんでした。次に、サイト全体で最も重要な注意事項を読みました。

パフォーマンスが重要であり、それを取得するためのコードを増やす必要がない場合は、これが最良の選択です。 JsonReader/JsonWriterの使用の詳細については、こちらをご覧ください

だから私はアドバイスに耳を傾け、文字列を効率的に読み取るために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

40
Faris Zacina

私は現在、ZenCoderとmythzの両方による提案を使用し、さらにテストを行いました。リリースモードでツールをビルドしている間に、Visual Studioからテストアプリを起動したため、デバッグオーバーヘッドが追加され、JSON.Netでさらに大きな違いが生じたため、最初のテストセットアップでもエラーが発生しました。私のPCのSOAP XML側と比較して、最初のテスト結果の実際の違いは既にかなり小さくなっています。

いずれにしても、以下は、それらをモデルにマッピングすることを含め、サーバー(localhost)から5000/50000ルームを収集した結果です。

5000部屋:

----- 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部屋:

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

伝説:

  • JSON.Net(リフレクション)-> JsonConvert.DeserializeObject(上記と同じJSON.Netコード)
  • ServiceStack(リフレクション)-> JsonSerializer.DeserializeFromString
  • SOAP Xml(手動マッピング)->同じSOAP DTOからモデルへのマッピングを追加した上記のクライアント呼び出し
  • JSON.Net(手動マッピング)->上記のZenCoderのコードに基づくコードを使用してJSONをモデルに直接マッピングし、リクエスト全体のマッピングを含めるように拡張しました(部屋と場所も)

  • ServiceStack(手動マッピング)->以下のコードを参照(例に基づいて: 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"),
                    }),
                }),
            });
    

メモ/個人的な結論:

  • リフレクションベースのデシリアライゼーションでさえ、SOAP実際のリリースモードでのXMLオブジェクト生成(oops)ほど遅くはありません。
  • JSON.Netの手動マッピングは自動マッピングよりも高速で、速度はSOAP Xmlマッピングのパフォーマンスに非常に匹敵し、多くの自由度を提供します。これは特にモデルおよびDTOの場合に優れています。場所が異なる
  • 実際には、ServiceStackの手動マッピングは、完全なリフレクションベースのマッピングよりも低速です。これは、JSON.Net側よりも高いレベルの手動マッピングであるためだと推測しています。これは、オブジェクトの生成が既に行われているように見えるためです。おそらく、ServiceStack側にも低レベルの選択肢がありますか?
  • これはすべて、同じマシンで実行されているサーバー/クライアントコードで実行されました。個別のクライアント/サーバーの実稼働環境では、JSONソリューションは、ネットワーク経由で送信する必要のあるメッセージがはるかに小さいため、SOAP XMLを上回るはずです。
  • この状況では、JSON.Netの自動マッピングは、大きな応答に対してServiceStackよりも少し高速であるように見えます。
4
Colin B
var receivedObject = JsonConvert.DeserializeObject<dynamic>(content);

私にとってははるかに速く動作します:

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

これはさらに高速です:

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

ここに2つのポイントを追加します。これは、IoTアプリケーションのパフォーマンスを向上させるのに役立ちます。毎日何百万ものJSONメッセージを受信して​​いました。

  1. ContractResolverインスタンスの変更

古いコード

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

新しいコード

すべての呼び出しでコントラクトリゾルバーインスタンスを作成せず、代わりに単一のインスタンスを使用します

return JsonConvert.SerializeObject(this, Formatting.Indented,
                          new JsonSerializerSettings
                          {
                              ContractResolver = AppConfiguration.CamelCaseResolver
                          });
  1. JObjectの作成を避ける

古いコード

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

パフォーマンスの利点を確認するために、benchmarkdotnetを使用して違いを確認しました。これもチェックしてください link も。

0
Pankaj Rawat