Allocation free XML serializer/deserializer based on C# incremental source generators (ISG). Because of ISG no performance issues occurs when working with big codebases.
Status: prototype.
Restrictions now:
- Do not support CDATA.
- Do not support malformed XML documents. No guard from malformed XML documents exists. Do not use it with potentially malicious XML documents.
- Serialized object must have a parameterless contructor and must be visible from the deserialization class.
- Serialized fields/properties must have accessible setter for deserialization.
- Only
List<T>
andT[]
supports as collections.
- Tuned for no (or minimum) memory allocation.
- Deserializer can accept a custom object factories which is useful for reuse already allocated objects (see
XmlFactory
attribute in the performance section below).
For the following XML document:
<InfoContainer>
<InfoCollection>
<BaseInfo xmlns:p3="http://www.w3.org/2001/XMLSchema-instance" p3:type="Derived3Info">
<Email>example@example.com</Email>
</BaseInfo>
<BaseInfo xmlns:p3="http://www.w3.org/2001/XMLSchema-instance" p3:type="Derived1Info">
<BasePersonificationInfo>my string !@#$%^&*()_+|-=\';[]{},./<>?</BasePersonificationInfo>
</BaseInfo>
<BaseInfo xmlns:p3="http://www.w3.org/2001/XMLSchema-instance" p3:type="Derived2Info">
<HotKeyUsed>false</HotKeyUsed>
<StepsCounter>1</StepsCounter>
<EventsTime>
<SerializeKeyValue>
<Key>Three</Key>
<Value>
<StartTime>2022-09-28T14:51:39.2438815+03:00</StartTime>
<SecondsSpan>3</SecondsSpan>
</Value>
</SerializeKeyValue>
<SerializeKeyValue>
<Key>One</Key>
<Value>
<StartTime>2022-09-28T14:28:00.5009069+03:00</StartTime>
<SecondsSpan>0</SecondsSpan>
</Value>
</SerializeKeyValue>
<SerializeKeyValue>
<Key>Two</Key>
<Value>
<StartTime>2022-09-28T14:28:02.3089553+03:00</StartTime>
<SecondsSpan>1</SecondsSpan>
</Value>
</SerializeKeyValue>
</EventsTime>
</BaseInfo>
</InfoCollection>
</InfoContainer>
the performance is:
BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1702/22H2/2022Update/SunValley2)
13th Gen Intel Core i7-13700H, 1 CPU, 20 logical and 14 physical cores
.NET SDK=7.0.300-preview.23179.2
[Host] : .NET 6.0.15 (6.0.1523.11507), X64 RyuJIT AVX2
.NET 7.0 : .NET 7.0.4 (7.0.423.11508), X64 RyuJIT AVX2
Job=.NET 7.0 Runtime=.NET 7.0
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|------------------------------- |---------:|----------:|----------:|-------:|-------:|----------:|
| 'Serialize: System.Xml' | 3.676 us | 0.0351 us | 0.0293 us | 1.0300 | 0.0343 | 12960 B |
| 'Serialize: XmlSerDe' | 1.278 us | 0.0185 us | 0.0173 us | 0.5512 | - | 6920 B |
| 'Serialize: XmlSerDe (est)' | 1.207 us | 0.0095 us | 0.0084 us | 0.3910 | 0.0019 | 4928 B |
| 'Serialize: XmlSerDe (stream)' | 1.289 us | 0.0026 us | 0.0022 us | 0.0458 | - | 592 B |
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|------------------------------- |---------:|----------:|----------:|-------:|-------:|----------:|
| 'Deserialize: System.Xml' | 7.265 us | 0.0404 us | 0.0359 us | 1.2741 | 0.0610 | 16072 B |
| 'Deserialize: XmlSerDe' | 5.437 us | 0.0486 us | 0.0455 us | 0.0610 | - | 824 B |
PTAL on few points:
(est)
test do premature estimation of result XML document length (via serialization to dev/null), and allocate the buffer of appropriate size. Serialization with estimation on may be a bit slower than a regular one, but it allocate less.(stream)
test serializes the data into the binary for sending into stream and\or network. For test purposes we do not send the data anywhere. Low allocations are possible because ofArrayPool<>.Rent
use.- Deserialization process is a bit faster and allocate only 5% memory in comparison to the standard serializer.
the code:
public InfoContainer Deserialize(ReadOnlySpan<char> xml)
{
XmlSerializerDeserializer.Deserialize(DefaultInjector.Instance, xml, out InfoContainer r);
return r;
}
public string Serialize()
{
var dsbe = new DefaultStringBuilderExhauster();
XmlSerializerDeserializer.Serialize(dsbe, DefaultObject, false);
var xml = dsbe.ToString();
return xml;
}
public string Serialize_Est()
{
//estimation phase:
var dlee = new DefaultLengthEstimatorExhauster();
XmlSerializerDeserializer.Serialize(dlee, DefaultObject, false);
var estimateXmlLength = dlee.EstimatedTotalLength;
//serialization phase:
var dsbe = new DefaultStringBuilderExhauster(
new StringBuilder(estimateXmlLength)
);
XmlSerializerDeserializer.Serialize(dsbe, DefaultObject, false);
var xml = dsbe.ToString();
return xml;
}
public void Serialize_ToStream_Test()
{
var be = new Utf8BinaryExhausterEmpty(
);
XmlSerializerDeserializer.Serialize(be, DefaultObject, false);
}
//...
public class Utf8BinaryExhausterEmpty : Utf8BinaryExhauster
{
protected override void Write(byte[] data, int length)
{
//nothing to do in tests
}
}
[XmlExhauster(typeof(DefaultLengthEstimatorExhauster))]
[XmlExhauster(typeof(DefaultStringBuilderExhauster))]
[XmlExhauster(typeof(Utf8BinaryExhausterEmpty))]
[XmlSubject(typeof(SerializeKeyValue), false)]
[XmlSubject(typeof(PerformanceTime), false)]
[XmlSubject(typeof(InfoContainer), true)]
[XmlSubject(typeof(BaseInfo), false)]
[XmlDerivedSubject(typeof(BaseInfo), typeof(Derived1Info))]
[XmlSubject(typeof(Derived1Info), false)]
[XmlDerivedSubject(typeof(BaseInfo), typeof(Derived2Info))]
[XmlSubject(typeof(Derived2Info), false)]
[XmlDerivedSubject(typeof(BaseInfo), typeof(Derived3Info))]
[XmlSubject(typeof(Derived3Info), false)]
[XmlFactory(typeof(InfoContainer), "global::" + "XmlSerDe.Tests.Complex.Subject" + "." + nameof(CachedInfoContainer) + "." + nameof(CachedInfoContainer.Reuse) + "()")]
public partial class XmlSerializerDeserializer
{
}
TODO in general
TODO: what attributes means
TODO in general
TODO: what is injector; embedded injector; custom injector; using custom injector to change deserialization format (for DateTime for example).
TODO in general
TODO: what is exhauster; embedded exhauster; custom exhauster; using custom exhauster to change serialization format (for DateTime for example).
You may be interested in the following project: https://github.com/ZingBallyhoo/StackXML