During performance testing of a very high throughput application we found a problem with JSON.NET's ContractResolver
. Unfortunately, it appears that when you specify a ContractResolver
the performance becomes unbearable, INCLUDING the DefaultContractResolver
Looking for advice from other experts out there to any suggestions around how to get performance to not lock down the CPU and eat up an unreasonable amount of time. Right now we are seeing an 87% reduction in performance due to this issue (80 requests per second with any ContractResolver
defined and 600 requests per second with no ContractResolver
defined.
The output of the test runs was:
Default Resolver: Time elapsed 3736 milliseconds
NoOp Resolver: Time elapsed 4150 milliseconds
No Resolver: Time elapsed 8 milliseconds
SnakeCase: Time elapsed 4753 milliseconds
Third Party (SnakeCase.JsonNet): Time elapsed 3881 milliseconds
The test to highlight this is as follows:
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");
}
}
}