在对非常高吞吐量的应用程序进行性能测试期间,我们发现JSON.NET的ContractResolver存在问题。不幸的是,当您指定ContractResolver时,性能似乎变得难以忍受,包括DefaultContractResolver

寻求其他专家的建议,以获取有关如何获得性能以不锁定CPU并浪费不合理时间的任何建议。目前,由于此问题,我们发现性能降低了87%(定义了任何ContractResolver的速度为每秒80个请求,而没有ContractResolver的速度为每秒600个请求
定义。

测试运行的输出为:

默认解析器:Time elapsed 3736 milliseconds

NoOp解析器:Time elapsed 4150 milliseconds

无解析器:Time elapsed 8 milliseconds

SnakeCase:Time elapsed 4753 milliseconds

第三方(SnakeCase.JsonNet):Time elapsed 3881 milliseconds

强调这一点的测试如下:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using SnakeCase.JsonNet;

namespace Anonymous.Public.Namespace
{
    public class Person
    {
        public string Name { get; set; }
        public DateTime DateOfBirth { get; set; }
        public bool EatsMeat { get; set; }
        public decimal YearlyHouseholdIncome { get; set; }

        public List<Car> CarList { get; set; }
    }

    public class Car
    {
        public string Make { get; set; }
        public string Model { get; set; }
        public int Year { get; set; }
    }

    public class NoOpNamingStrategy : NamingStrategy
    {
        public NoOpNamingStrategy(bool processDictionaryKeys, bool overrideSpecifiedNames)
        {
            base.ProcessDictionaryKeys = processDictionaryKeys;
            base.OverrideSpecifiedNames = overrideSpecifiedNames;
        }

        public NoOpNamingStrategy(bool processDictionaryKeys, bool overrideSpecifiedNames, bool processExtensionDataNames) : this(processDictionaryKeys, overrideSpecifiedNames)
        {
            base.ProcessExtensionDataNames = processExtensionDataNames;
        }

        public NoOpNamingStrategy()
        {
        }

        protected override string ResolvePropertyName(string name)
        {
            return name;
        }
    }

    [TestClass]
    public class SerializationTest
    {
        public static Person p { get; set; } = new Person
        {
            Name = "Foo Bar",
            DateOfBirth = new DateTime(1970, 01, 01),
            EatsMeat = true,
            YearlyHouseholdIncome = 47333M,
            CarList = new List<Car>
            {
                new Car
                {
                    Make = "Honda",
                    Model = "Civic",
                    Year = 2019
                }
            }
        };

        public const int ITERATIONS = 1000;

        [TestMethod]
        public void TestSnakeCase()
        {
            var sw = new Stopwatch();
            sw.Start();
            for (var i = 0; i < ITERATIONS; i++)
            {
                var str = JsonConvert.SerializeObject(p, new JsonSerializerSettings
                {
                    ContractResolver = new DefaultContractResolver
                    {
                        NamingStrategy = new SnakeCaseNamingStrategy()
                    }
                });
            }

            sw.Stop();
            var elapsed = sw.ElapsedMilliseconds;
            Debug.WriteLine($"Time elapsed {elapsed} milliseconds");
        }

        [TestMethod]
        public void TestNoResolver()
        {
            var sw = new Stopwatch();
            sw.Start();
            for (var i = 0; i < ITERATIONS; i++)
            {
                var str = JsonConvert.SerializeObject(p);
            }

            sw.Stop();
            var elapsed = sw.ElapsedMilliseconds;
            Debug.WriteLine($"Time elapsed {elapsed} milliseconds");
        }

        [TestMethod]
        public void TestDefaultResolver()
        {
            var sw = new Stopwatch();
            sw.Start();
            for (var i = 0; i < ITERATIONS; i++)
            {
                var str = JsonConvert.SerializeObject(p, new JsonSerializerSettings
                {
                    ContractResolver = new DefaultContractResolver()
                });
            }

            sw.Stop();
            var elapsed = sw.ElapsedMilliseconds;
            Debug.WriteLine($"Time elapsed {elapsed} milliseconds");
        }

        [TestMethod]
        public void TestThirdPartySnakeResolver()
        {
            var sw = new Stopwatch();
            sw.Start();
            for (var i = 0; i < ITERATIONS; i++)
            {
                var str = JsonConvert.SerializeObject(p, new JsonSerializerSettings
                {
                    ContractResolver = new SnakeCaseContractResolver()
                });
            }

            sw.Stop();
            var elapsed = sw.ElapsedMilliseconds;
            Debug.WriteLine($"Time elapsed {elapsed} milliseconds");
        }

        [TestMethod]
        public void TestNoOpResolver()
        {
            var sw = new Stopwatch();
            sw.Start();
            for (var i = 0; i < ITERATIONS; i++)
            {
                var str = JsonConvert.SerializeObject(p, new JsonSerializerSettings
                {
                    ContractResolver = new DefaultContractResolver
                    {
                        NamingStrategy = new NoOpNamingStrategy()
                    }
                });
            }

            sw.Stop();
            var elapsed = sw.ElapsedMilliseconds;
            Debug.WriteLine($"Time elapsed {elapsed} milliseconds");
        }
    }
}

最佳答案

似乎ContractResolver需要反射,如果保留,它将缓存对象类型。将ContractResolver存储在全局范围内会极大地将时间更改为:

默认解析器:Time elapsed 10 milliseconds

NoOp解析器:Time elapsed 7 milliseconds

无解析器:Time elapsed 7 milliseconds

SnakeCase:Time elapsed 178 milliseconds

第三方(SnakeCase.JsonNet):Time elapsed 10 milliseconds

更新的测试代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using SnakeCase.JsonNet;

namespace Anonymous.Public.Namespace
{
    public class Person
    {
        public string Name { get; set; }
        public DateTime DateOfBirth { get; set; }
        public bool EatsMeat { get; set; }
        public decimal YearlyHouseholdIncome { get; set; }

        public List<Car> CarList { get; set; }
    }

    public class Car
    {
        public string Make { get; set; }
        public string Model { get; set; }
        public int Year { get; set; }
    }
    public class NoOpNamingStrategy : NamingStrategy
    {
        public NoOpNamingStrategy(bool processDictionaryKeys, bool overrideSpecifiedNames)
        {
            base.ProcessDictionaryKeys = processDictionaryKeys;
            base.OverrideSpecifiedNames = overrideSpecifiedNames;
        }

        public NoOpNamingStrategy(bool processDictionaryKeys, bool overrideSpecifiedNames, bool processExtensionDataNames) : this(processDictionaryKeys, overrideSpecifiedNames)
        {
            base.ProcessExtensionDataNames = processExtensionDataNames;
        }

        public NoOpNamingStrategy()
        {
        }

        protected override string ResolvePropertyName(string name)
        {
            return name;
        }
    }

    [TestClass]
    public class SerializationTest
    {
        public static Person p { get; set; } = new Person
        {
            Name = "Foo Bar",
            DateOfBirth = new DateTime(1970, 01, 01),
            EatsMeat = true,
            YearlyHouseholdIncome = 47333M,
            CarList = new List<Car>
            {
                new Car
                {
                    Make = "Honda",
                    Model = "Civic",
                    Year = 2019
                }
            }
        };

        public static IContractResolver Default { get; set; } = new DefaultContractResolver();

        public static IContractResolver NoOp { get; set; } = new DefaultContractResolver
        {
            NamingStrategy = new NoOpNamingStrategy()
        };

        public static IContractResolver SnakeCase { get; set; } = new DefaultContractResolver
        {
            NamingStrategy = new SnakeCaseNamingStrategy()
        };

        public static IContractResolver ThirdParty { get; set; } = new SnakeCaseContractResolver();

        public const int ITERATIONS = 1000;

        [TestMethod]
        public void TestSnakeCase()
        {
            var sw = new Stopwatch();
            sw.Start();
            for (var i = 0; i < ITERATIONS; i++)
            {
                var str = JsonConvert.SerializeObject(p, new JsonSerializerSettings
                {
                    ContractResolver = SnakeCase
                });
            }

            sw.Stop();
            var elapsed = sw.ElapsedMilliseconds;
            Debug.WriteLine($"Time elapsed {elapsed} milliseconds");
        }

        [TestMethod]
        public void TestNoResolver()
        {
            var sw = new Stopwatch();
            sw.Start();
            for (var i = 0; i < ITERATIONS; i++)
            {
                var str = JsonConvert.SerializeObject(p);
            }

            sw.Stop();
            var elapsed = sw.ElapsedMilliseconds;
            Debug.WriteLine($"Time elapsed {elapsed} milliseconds");
        }

        [TestMethod]
        public void TestDefaultResolver()
        {
            var sw = new Stopwatch();
            sw.Start();
            for (var i = 0; i < ITERATIONS; i++)
            {
                var str = JsonConvert.SerializeObject(p, new JsonSerializerSettings
                {
                    ContractResolver = Default
                });
            }

            sw.Stop();
            var elapsed = sw.ElapsedMilliseconds;
            Debug.WriteLine($"Time elapsed {elapsed} milliseconds");
        }

        [TestMethod]
        public void TestThirdPartySnakeResolver()
        {
            var sw = new Stopwatch();
            sw.Start();
            for (var i = 0; i < ITERATIONS; i++)
            {
                var str = JsonConvert.SerializeObject(p, new JsonSerializerSettings
                {
                    ContractResolver = ThirdParty
                });
            }

            sw.Stop();
            var elapsed = sw.ElapsedMilliseconds;
            Debug.WriteLine($"Time elapsed {elapsed} milliseconds");
        }

        [TestMethod]
        public void TestNoOpResolver()
        {
            var sw = new Stopwatch();
            sw.Start();
            for (var i = 0; i < ITERATIONS; i++)
            {
                var str = JsonConvert.SerializeObject(p, new JsonSerializerSettings
                {
                    ContractResolver = NoOp
                });
            }

            sw.Stop();
            var elapsed = sw.ElapsedMilliseconds;
            Debug.WriteLine($"Time elapsed {elapsed} milliseconds");
        }
    }
}

10-01 09:41