在对非常高吞吐量的应用程序进行性能测试期间,我们发现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");
}
}
}