Skip to content

Latest commit

 

History

History
168 lines (127 loc) · 6.79 KB

Performance.md

File metadata and controls

168 lines (127 loc) · 6.79 KB

Performance

Reuse Contract Resolver

The Argon.IContractResolver resolves .NET types to contracts that are used during serialization inside JsonSerializer. Creating a contract involves inspecting a type with slow reflection, so contracts are typically cached by implementations of IContractResolver like Argon.DefaultContractResolver.

To avoid the overhead of recreating contracts every time a JsonSerializer is used create the contract resolver once and reuse it. Note that if not using a contract resolver then a shared internal instance is automatically used when serializing and deserializing.

// BAD - a new contract resolver is created each time, forcing slow reflection to be used
JsonConvert.SerializeObject(person, new JsonSerializerSettings
{
    Formatting = Formatting.Indented,
    ContractResolver = new DefaultContractResolver
    {
        NamingStrategy = new SnakeCaseNamingStrategy()
    }
});

// GOOD - reuse the contract resolver from a shared location
JsonConvert.SerializeObject(person, new JsonSerializerSettings
{
    Formatting = Formatting.Indented,
    ContractResolver = AppSettings.SnakeCaseContractResolver
});

// GOOD - an internal contract resolver is used
JsonConvert.SerializeObject(person, new JsonSerializerSettings
{
    Formatting = Formatting.Indented
});

snippet source | anchor

Optimize Memory Usage

To keep an application consistently fast, it is important to minimize the amount of time the .NET framework spends performing garbage collection

Allocating too many objects or allocating very large objects can slow down or even halt an application while garbage collection is in progress.

To minimize memory usage and the number of objects allocated, Json.NET supports serializing and deserializing directly to a stream. Reading or writing JSON a piece at a time, instead of having the entire JSON string loaded into memory, is especially important when working with JSON documents greater than 85kb in size to avoid the JSON string ending up in the large object heap

var client = new HttpClient();

// read the json into a string
// string could potentially be very large and cause memory problems
var json = await client.GetStringAsync("http://www.test.com/large.json");

var p = JsonConvert.DeserializeObject<Person>(json);

snippet source | anchor

using var streamReader = new StreamReader(stream);
using var reader = new JsonTextReader(streamReader);
var serializer = new JsonSerializer();

// read the json from a stream
// json size doesn't matter because only a small piece is read at a time from the stream
var p = serializer.Deserialize<Person>(reader);

snippet source | anchor

JsonConverters

Passing a Argon.JsonConverter to SerializeObject or DeserializeObject provides a way to completely change how an object is serialized. There is, however, a small amount of overhead; the CanConvert method is called for every value to check whether serialization should be handled by that JsonConverter.

There are a couple of ways to continue to use JsonConverters without any overhead. The simplest way is to specify the JsonConverter using the Argon.JsonConverterAttribute. This attribute tells the serializer to always use that converter when serializing and deserializing the type, without the check.

[JsonConverter(typeof(PersonConverter))]
public class Person
{
    public string Name { get; set; }
    public IList<string> Likes { get; } = [];
}

snippet source | anchor

If the class to convert isn't owned and is it nor possible to use an attribute, a JsonConverter can still be used by creating a Argon.IContractResolver.

//TODO

public class ConverterContractResolver : DefaultContractResolver
{
    public new static readonly ConverterContractResolver Instance = new();

    protected override JsonContract CreateContract(Type type)
    {
        var contract = base.CreateContract(type);

        // this will only be called once and then cached
        if (type == typeof(DateTime) || type == typeof(DateTimeOffset))
        {
            contract.Converter = new JavaScriptDateTimeConverter();
        }

        return contract;
    }
}

The IContractResolver in the example above will set all DateTimes to use the JavaScriptDateConverter.

Manually Serialize

The absolute fastest way to read and write JSON is to use JsonTextReader/JsonTextWriter directly to manually serialize types. Using a reader or writer directly skips any of the overhead from a serializer, such as reflection.

public static string ToJson(this Person p)
{
    var stringWriter = new StringWriter();
    var jsonWriter = new JsonTextWriter(stringWriter);

    // {
    jsonWriter.WriteStartObject();

    // "name" : "Jerry"
    jsonWriter.WritePropertyName("name");
    jsonWriter.WriteValue(p.Name);

    // "likes": ["Comedy", "Superman"]
    jsonWriter.WritePropertyName("likes");
    jsonWriter.WriteStartArray();
    foreach (var like in p.Likes)
    {
        jsonWriter.WriteValue(like);
    }

    jsonWriter.WriteEndArray();

    // }
    jsonWriter.WriteEndObject();

    return stringWriter.ToString();
}

snippet source | anchor

If performance is important, then this is the best choice. More about using JsonReader/JsonWriter here: [ReadingWritingJSON]

Related Topics

  • Argon.JsonSerializer
  • Argon.JsonConverter
  • Argon.JsonConverterAttribute
  • Argon.JsonTextWriter
  • Argon.JsonTextReader