Following this post I created [SnakeCase]
ActionFilterAttribute, so some of my controllers return JSON
s in snake_case
.
It works, however what I've found is that it consumes lot of CPU.
Original code:
public class SnakeCaseAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
if (context.Result is ObjectResult objectResult)
{
objectResult.Formatters.Add(new NewtonsoftJsonOutputFormatter(
new JsonSerializerSettings // this is the culprit
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy
{
OverrideSpecifiedNames = false
},
},
},
context.HttpContext.RequestServices.GetRequiredService<ArrayPool<char>>(),
context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>().Value));
}
else
{
base.OnActionExecuted(context);
}
}
}
It took me few days to find, that following code is about x10-x15 faster!
public class SnakeCaseAttribute : ActionFilterAttribute
{
private static JsonSerializerSettings jss = new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy
{
OverrideSpecifiedNames = false
},
},
};
public override void OnActionExecuted(ActionExecutedContext context)
{
if (context.Result is ObjectResult objectResult)
{
objectResult.Formatters.Add(new NewtonsoftJsonOutputFormatter(
jss, // change is here!!!
context.HttpContext.RequestServices.GetRequiredService<ArrayPool<char>>(),
context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>().Value));
}
else
{
base.OnActionExecuted(context);
}
}
}
Unfortunately, I don't understand why it is so.
I did monitor my app with dotnet-counters
and I've found that for the slow case (1st example) there was (apart from huge CPU usage) this weird value:
Time spent in JIT (ms / 1 sec) 1051.12
(this value was fluctuating, but always around 1000)
When I changed the code, it is around ~10ms/s (100x times less):
Time spent in JIT (ms / 1 sec) 7.162
I suspect that with first example, when JsonSerializerSettings
is created every time, it somehow triggers lot of JIT compilation that was using lot of CPU.
I would like to understand this mechanism, and what is really going on inside Newtonsoft's code in that case.