如何在 .Net 中提高 JSON 反序列化速度?(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)

我正在发布模式下运行应用程序。客户端和服务器在同一台计算机上。如您所见,与 WCF SOAP 使用的 XML 到对象映射相比,使用 JSON 对许多(相同类型的(对象进行反序列化要花费更多的时间。见鬼,单独反序列化比使用 SOAP 的整个 Web 服务调用花费的时间更多。

对此有解释吗?XML(或 WCF SOAP 实现(是否在这方面提供了很大的优势,或者我可以在客户端更改任何内容(我宁愿不更改服务,但更改客户端 DTO 是可以接受的(以尝试提高性能?感觉我已经在 JSON.net 端选择了一些应该比默认设置更快的设置,不是吗?这里的瓶颈似乎是什么?

我花了更多的时间阅读 JSON.NET 内部结构,我的结论是缓慢主要是由反射引起的。

在 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

我现在已经使用了The ZenCoder和mythz的建议,并且我做了更多的测试。我在第一次测试设置中也注意到了一个错误,因为当我在发布模式下构建该工具时,我仍然从 Visual Studio 启动测试应用程序,这仍然增加了一些调试开销,与 PC 上的 SOAP XML 端相比,这在 JSON.Net 端产生了更大的差异,因此初始测试结果的实际差异已经小了很多。

无论哪种方式,以下是从服务器(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 (reflection( -> 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 对象生成慢多少(哎呀(
  • JSON.Net 中的手动映射比自动映射更快,并且在速度上与SOAP XML映射性能非常相似,并且它提供了很大的自由度,这很棒,特别是当模型和DTO在某些地方不同时
  • ServiceStack手动映射实际上比完全基于反射的映射慢。我猜这是因为它是比 JSON.Net 端更高级别的手动映射,因为那里似乎已经发生了一些对象生成。也许在ServiceStack方面也有较低级别的替代方案?
  • 所有这些都是通过在同一台机器上运行的服务器/客户端代码完成的。在单独的客户端/服务器生产环境中,我确信JSON解决方案应该击败SOAP XML,因为需要通过网络发送的消息要小得多。
  • 在这种情况下,对于大响应,JSON.Net 自动映射似乎比ServiceStack快一点。
var receivedObject = JsonConvert.DeserializeObject<dynamic>(content);

那么对我来说效果要快得多:

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

这甚至更快:

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

我在这里再添加 2 点,这有助于我提高我的物联网应用程序性能。我每天收到数百万条JSON消息。

  1. 协定解析程序实例中的更改

旧代码

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

新代码

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

为了检查性能优势,我使用 benchmarkdotnet 来查看差异。 也请查看此链接。

相关内容

  • 没有找到相关文章

最新更新