Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve allocations in ReflectionXmlSerializationWriter when serializing primitive types #84474

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,11 @@ private void WriteText(object o, TextAccessor text)
}
else
{
if (TryWritePrimitiveValue(primitiveMapping.TypeDesc!, o))
{
return;
}

if (!WritePrimitiveValue(primitiveMapping.TypeDesc!, o, out stringValue))
{
Debug.Assert(o is byte[]);
Expand Down Expand Up @@ -808,6 +813,10 @@ private void WriteMember(object? memberValue, AttributeAccessor attribute, TypeD
}
else
{
if (TryWritePrimitiveValue(arrayElementTypeDesc!, ai))
{
continue;
}
if (!WritePrimitiveValue(arrayElementTypeDesc!, ai, out stringValue))
{
Debug.Assert(ai is byte[]);
Expand Down Expand Up @@ -983,6 +992,10 @@ private void WritePrimitive(WritePrimitiveMethodRequirement method, string name,
}
else
{
if (TryWritePrimitiveValue(method, name, ns, typeDesc, o, xmlQualifiedName))
{
return;
}
hasValidStringValue = WritePrimitiveValue(typeDesc, o, out stringValue);
}

Expand Down Expand Up @@ -1191,6 +1204,109 @@ private static string ConvertPrimitiveToString(object o, TypeDesc typeDesc)
return stringValue;
}

private static bool TryFormatPrimitiveValue(string? formatterName, bool hasCustomFormatter, object? o, Span<char> destination, out int charsWritten)
{
charsWritten = 0;
if (o == null || formatterName == "String")
{
return false;
}

if (!hasCustomFormatter)
{
switch (formatterName)
{
case "Boolean":
return XmlConvert.TryFormat((bool)o, destination, out charsWritten);
case "Int32":
return XmlConvert.TryFormat((int)o, destination, out charsWritten);
case "Int16":
return XmlConvert.TryFormat((short)o, destination, out charsWritten);
case "Int64":
return XmlConvert.TryFormat((long)o, destination, out charsWritten);
case "Single":
return XmlConvert.TryFormat((float)o, destination, out charsWritten);
case "Double":
return XmlConvert.TryFormat((double)o, destination, out charsWritten);
case "Decimal":
return XmlConvert.TryFormat((decimal)o, destination, out charsWritten);
case "Byte":
return XmlConvert.TryFormat((byte)o, destination, out charsWritten);
case "SByte":
return XmlConvert.TryFormat((sbyte)o, destination, out charsWritten);
case "UInt16":
return XmlConvert.TryFormat((ushort)o, destination, out charsWritten);
case "UInt32":
return XmlConvert.TryFormat((uint)o, destination, out charsWritten);
case "UInt64":
return XmlConvert.TryFormat((ulong)o, destination, out charsWritten);
// Types without direct mapping (ambiguous)
case "Guid":
return XmlConvert.TryFormat((Guid)o, destination, out charsWritten);
case "Char":
return TryFormatChar((char)o, destination, out charsWritten);
case "TimeSpan":
return XmlConvert.TryFormat((TimeSpan)o, destination, out charsWritten);
case "DateTimeOffset":
return XmlConvert.TryFormat((DateTimeOffset)o, destination, out charsWritten);
default:
return false;
}
}

switch (o)
{
case DateTime dt when formatterName == "DateTime":
return TryFormatDateTime(dt, destination, out charsWritten);
case DateTime d when formatterName == "Date":
return TryFormatDate(d, destination, out charsWritten);
case DateTime t when formatterName == "Time":
return TryFormatTime(t, destination, out charsWritten);
case DateTime:
throw new InvalidOperationException(SR.Format(SR.XmlInternalErrorDetails, "Invalid DateTime"));
case char c when formatterName == "Char":
return TryFormatChar(c, destination, out charsWritten);
default:
return false;
}
}

private bool TryWritePrimitiveValue(WritePrimitiveMethodRequirement method, string name, string? ns, TypeDesc typeDesc, object? o, XmlQualifiedName? xmlQualifiedName)
{
if (typeDesc == ReflectionXmlSerializationReader.StringTypeDesc
|| hasRequirement(method, WritePrimitiveMethodRequirement.WriteNullableStringLiteral)) return false;

bool result = TryFormatPrimitiveValue(typeDesc.FormatterName, typeDesc.HasCustomFormatter, o, primitivesBuffer, out int charsWritten);
if (result)
{
if (hasRequirement(method, WritePrimitiveMethodRequirement.WriteElementString))
{
WriteElementStringRaw(name, ns, primitivesBuffer, 0, charsWritten, xmlQualifiedName);
}
else if (hasRequirement(method, WritePrimitiveMethodRequirement.WriteAttribute))
{
WriteAttribute(name, ns, primitivesBuffer, 0, charsWritten);
}
else
{
Debug.Fail("https://github.com/dotnet/runtime/issues/18037: Add More Tests for Serialization Code");
}
}

return result;
}

private bool TryWritePrimitiveValue(TypeDesc typeDesc, object? o)
{
if (typeDesc == ReflectionXmlSerializationReader.StringTypeDesc) return false;

bool result = TryFormatPrimitiveValue(typeDesc.FormatterName, typeDesc.HasCustomFormatter, o, primitivesBuffer, out int charsWritten);
if (result)
WriteValue(primitivesBuffer, 0, charsWritten);

return result;
}

[RequiresUnreferencedCode("calls WritePotentiallyReferencingElement")]
private void GenerateMembersElement(object o, XmlMembersMapping xmlMembersMapping)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public abstract class XmlSerializationWriter : XmlSerializationGeneratedCode
private bool _escapeName = true;

//char buffer for serializing primitive values
private readonly char[] _primitivesBuffer = new char[64];
protected readonly char[] primitivesBuffer = new char[64];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is optional - can be private and then we just have another buffer here.


// this method must be called before any generated serialization methods are called
internal void Init(XmlWriter w, XmlSerializerNamespaces? namespaces, string? encodingStyle, string? idBase)
Expand Down Expand Up @@ -133,16 +133,31 @@ protected static string FromDate(DateTime value)
return XmlCustomFormatter.FromDate(value);
}

internal static bool TryFormatDate(DateTime value, Span<char> destination, out int charsWritten)
{
return XmlCustomFormatter.TryFormatDate(value, destination, out charsWritten);
}

protected static string FromTime(DateTime value)
{
return XmlCustomFormatter.FromTime(value);
}

internal static bool TryFormatTime(DateTime value, Span<char> destination, out int charsWritten)
{
return XmlCustomFormatter.TryFormatTime(value, destination, out charsWritten);
}

protected static string FromChar(char value)
{
return XmlCustomFormatter.FromChar(value);
}

internal static bool TryFormatChar(char value, Span<char> destination, out int charsWritten)
{
return XmlCustomFormatter.TryFormatChar(value, destination, out charsWritten);
}

protected static string FromEnum(long value, string[] values, long[] ids)
{
return XmlCustomFormatter.FromEnum(value, values, ids, null);
Expand Down Expand Up @@ -272,60 +287,60 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT
writeRaw = false;
break;
case TypeCode.Int32:
tryFormatResult = XmlConvert.TryFormat((int)o, _primitivesBuffer, out charsWritten);
tryFormatResult = XmlConvert.TryFormat((int)o, primitivesBuffer, out charsWritten);
type = "int";
break;
case TypeCode.Boolean:
tryFormatResult = XmlConvert.TryFormat((bool)o, _primitivesBuffer, out charsWritten);
tryFormatResult = XmlConvert.TryFormat((bool)o, primitivesBuffer, out charsWritten);
type = "boolean";
break;
case TypeCode.Int16:
tryFormatResult = XmlConvert.TryFormat((short)o, _primitivesBuffer, out charsWritten);
tryFormatResult = XmlConvert.TryFormat((short)o, primitivesBuffer, out charsWritten);
type = "short";
break;
case TypeCode.Int64:
tryFormatResult = XmlConvert.TryFormat((long)o, _primitivesBuffer, out charsWritten);
tryFormatResult = XmlConvert.TryFormat((long)o, primitivesBuffer, out charsWritten);
type = "long";
break;
case TypeCode.Single:
tryFormatResult = XmlConvert.TryFormat((float)o, _primitivesBuffer, out charsWritten);
tryFormatResult = XmlConvert.TryFormat((float)o, primitivesBuffer, out charsWritten);
type = "float";
break;
case TypeCode.Double:
tryFormatResult = XmlConvert.TryFormat((double)o, _primitivesBuffer, out charsWritten);
tryFormatResult = XmlConvert.TryFormat((double)o, primitivesBuffer, out charsWritten);
type = "double";
break;
case TypeCode.Decimal:
tryFormatResult = XmlConvert.TryFormat((decimal)o, _primitivesBuffer, out charsWritten);
tryFormatResult = XmlConvert.TryFormat((decimal)o, primitivesBuffer, out charsWritten);
type = "decimal";
break;
case TypeCode.DateTime:
tryFormatResult = TryFormatDateTime((DateTime)o, _primitivesBuffer, out charsWritten);
tryFormatResult = TryFormatDateTime((DateTime)o, primitivesBuffer, out charsWritten);
type = "dateTime";
break;
case TypeCode.Char:
tryFormatResult = XmlConvert.TryFormat((ushort)(char)o, _primitivesBuffer, out charsWritten);
tryFormatResult = XmlConvert.TryFormat((ushort)(char)o, primitivesBuffer, out charsWritten);
type = "char";
typeNs = UrtTypes.Namespace;
break;
case TypeCode.Byte:
tryFormatResult = XmlConvert.TryFormat((byte)o, _primitivesBuffer, out charsWritten);
tryFormatResult = XmlConvert.TryFormat((byte)o, primitivesBuffer, out charsWritten);
type = "unsignedByte";
break;
case TypeCode.SByte:
tryFormatResult = XmlConvert.TryFormat((sbyte)o, _primitivesBuffer, out charsWritten);
tryFormatResult = XmlConvert.TryFormat((sbyte)o, primitivesBuffer, out charsWritten);
type = "byte";
break;
case TypeCode.UInt16:
tryFormatResult = XmlConvert.TryFormat((ushort)o, _primitivesBuffer, out charsWritten);
tryFormatResult = XmlConvert.TryFormat((ushort)o, primitivesBuffer, out charsWritten);
type = "unsignedShort";
break;
case TypeCode.UInt32:
tryFormatResult = XmlConvert.TryFormat((uint)o, _primitivesBuffer, out charsWritten);
tryFormatResult = XmlConvert.TryFormat((uint)o, primitivesBuffer, out charsWritten);
type = "unsignedInt";
break;
case TypeCode.UInt64:
tryFormatResult = XmlConvert.TryFormat((ulong)o, _primitivesBuffer, out charsWritten);
tryFormatResult = XmlConvert.TryFormat((ulong)o, primitivesBuffer, out charsWritten);
type = "unsignedLong";
break;

Expand All @@ -350,19 +365,19 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT
}
else if (t == typeof(Guid))
{
tryFormatResult = XmlConvert.TryFormat((Guid)o, _primitivesBuffer, out charsWritten);
tryFormatResult = XmlConvert.TryFormat((Guid)o, primitivesBuffer, out charsWritten);
type = "guid";
typeNs = UrtTypes.Namespace;
}
else if (t == typeof(TimeSpan))
{
tryFormatResult = XmlConvert.TryFormat((TimeSpan)o, _primitivesBuffer, out charsWritten);
tryFormatResult = XmlConvert.TryFormat((TimeSpan)o, primitivesBuffer, out charsWritten);
type = "TimeSpan";
typeNs = UrtTypes.Namespace;
}
else if (t == typeof(DateTimeOffset))
{
tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)o, _primitivesBuffer, out charsWritten);
tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)o, primitivesBuffer, out charsWritten);
type = "dateTimeOffset";
typeNs = UrtTypes.Namespace;
}
Expand Down Expand Up @@ -410,11 +425,11 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT
Debug.Assert(tryFormatResult.Value, "Something goes wrong with formatting primitives to the buffer.");
#if DEBUG
const string escapeChars = "<>\"'&";
ReadOnlySpan<char> span = _primitivesBuffer;
ReadOnlySpan<char> span = primitivesBuffer;
Debug.Assert(span.Slice(0, charsWritten).IndexOfAny(escapeChars) == -1, "Primitive value contains illegal xml char.");
#endif
//all the primitive types except string and XmlQualifiedName writes to the buffer
_w.WriteRaw(_primitivesBuffer, 0, charsWritten);
_w.WriteRaw(primitivesBuffer, 0, charsWritten);
}
else
{
Expand Down Expand Up @@ -977,34 +992,56 @@ protected void WriteAttribute(string localName, string? ns, string? value)
}
}

protected void WriteAttribute(string localName, string ns, byte[]? value)
private bool TryWriteStartAttribute(string localName, string? ns)
{
if (value == null) return;

if (localName != "xmlns" && !localName.StartsWith("xmlns:", StringComparison.Ordinal))
if (localName == "xmlns" || localName.StartsWith("xmlns:", StringComparison.Ordinal))
{
int colon = localName.IndexOf(':');
return false;
}

if (colon < 0)
int colon = localName.IndexOf(':');

if (colon < 0)
{
if (ns == XmlReservedNs.NsXml)
{
if (ns == XmlReservedNs.NsXml)
{
_w.WriteStartAttribute("xml", localName, ns);
}
else
{
_w.WriteStartAttribute(null, localName, ns);
}
_w.WriteStartAttribute("xml", localName, ns);
}
else
{
string? prefix = _w.LookupPrefix(ns);
_w.WriteStartAttribute(prefix, localName.Substring(colon + 1), ns);
_w.WriteStartAttribute(null, localName, ns);
}
}
else
{
string? prefix = ns != null? _w.LookupPrefix(ns) : null;
_w.WriteStartAttribute(prefix, localName.Substring(colon + 1), ns);
}

return true;
}

protected void WriteAttribute(string localName, string ns, byte[]? value)
{
if (value == null ||
!TryWriteStartAttribute(localName, ns))
{
return;
}

XmlCustomFormatter.WriteArrayBase64(_w, value, 0, value.Length);
_w.WriteEndAttribute();
XmlCustomFormatter.WriteArrayBase64(_w, value, 0, value.Length);
_w.WriteEndAttribute();
}

protected void WriteAttribute(string localName, string? ns, char[] value, int index, int count)
{
if (!TryWriteStartAttribute(localName, ns))
{
return;
}

_w.WriteRaw(value, index, count);
_w.WriteEndAttribute();
}

protected void WriteAttribute(string localName, string? value)
Expand Down Expand Up @@ -1040,6 +1077,11 @@ protected void WriteValue(byte[]? value)
XmlCustomFormatter.WriteArrayBase64(_w, value, 0, value.Length);
}

protected void WriteValue(char[] value, int index, int count)
{
_w.WriteRaw(value, index, count);
}

protected void WriteStartDocument()
{
if (_w.WriteState == WriteState.Start)
Expand Down Expand Up @@ -1127,6 +1169,15 @@ protected void WriteElementStringRaw(string localName, string? ns, byte[]? value
_w.WriteEndElement();
}

protected void WriteElementStringRaw(string localName, string? ns, char[] value, int index, int count, XmlQualifiedName? xsiType)
{
_w.WriteStartElement(localName, ns);
if (xsiType != null)
WriteXsiType(xsiType.Name, xsiType.Namespace);
_w.WriteRaw(value, index, count);
_w.WriteEndElement();
}

protected void WriteRpcResult(string name, string? ns)
{
if (!_soap12) return;
Expand Down
Loading