2

Following this post I created [SnakeCase] ActionFilterAttribute, so some of my controllers return JSONs 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.

Maciej Pszczolinski
  • 1,623
  • 1
  • 19
  • 38
  • You just instantiated 3 objects everytime, have you checked the performance of their constructors? – shingo Feb 09 '23 at 12:07
  • Yes, I did. It is around 10ns (nanoseconds), meaning 0.01ms. Please note that only difference is `JsonSerializerSettings` and 2 other object are identical in both variants. – Maciej Pszczolinski Feb 09 '23 at 13:36
  • 1
    See [Does Json.NET cache types' serialization information?](https://stackoverflow.com/a/33558665/3744182) and [Performance Tips: Reuse Contract Resolver](https://www.newtonsoft.com/json/help/html/performance.htm#ReuseContractResolver). Contract creation (which uses reflection) is slow so the contract resolver caches and reuses the contract. If you construct a new resolver for each call, you lose that performance advantage. By using a static resolver you get the performance back again. – dbc Feb 09 '23 at 15:56
  • 1
    Thx @dbc, I have my answer. – Maciej Pszczolinski Feb 09 '23 at 16:58

0 Answers0