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

Add char[] buffer to XmlRawWriter. #75411

Conversation

TrayanZapryanov
Copy link
Contributor

@TrayanZapryanov TrayanZapryanov commented Sep 11, 2022

This PR suggest using small char[] buffer in XmlRawWriter and use it for formatting primitive types in it. Then using WriteRaw(char[]) method instead of creating temporary string and WriteRaw(string) method.
Also exposing XmlConverter.TryFormat methods for already exposed ToString(xxx) primitive types, allowing writes to Span destination.
Last change is to expose TryFormat in XsdDateTime and XsdDuration.

This PR will open possibilities in XmlSerializers to use typed methods of XmlWriter instead of always fallback to WriteString one.
Sample optimization

Expose TryFormat methods in XmlConvert and use them in XmlRawWriter.
Expose TryFormat in XsdDateTime and XsdDuration.
@ghost ghost added the community-contribution Indicates that the PR has been added by a community member label Sep 11, 2022
@ghost
Copy link

ghost commented Sep 11, 2022

Tagging subscribers to this area: @dotnet/area-system-xml
See info in area-owners.md if you want to be subscribed.

Issue Details

This PR suggest using small char[] buffer in XmlRawWriter and use it for formatting primitive types in it and then using WriteRaw(char[]) method instead of creating temporary string and using WriteRaw(string).
Also exposing TryFormat methods for already exposed ToString(xxx) primitive types, allowing writes to Span destination.
Last change is to expose TryFormat in XsdDateTime and XsdDuration.

This PR will open possibilities in XmlSerializers to use typed methods of XmlWriter instead of always fallback to WriteString one.

Author: TrayanZapryanov
Assignees: -
Labels:

area-System.Xml

Milestone: -

_primitivesBuffer = new char[_primitivesBuffer.Length * 2];
}

WriteChars(_primitivesBuffer, 0, charsWritten);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I saw that there is difference between

public override unsafe void WriteChars(char[] buffer, int index, int count)

and

public override unsafe void WriteRaw(char[] buffer, int index, int count)

Which one should be used ?

@@ -198,7 +265,7 @@ public override void WriteValue(DateTimeOffset value)
{
// For compatibility with custom writers, XmlWriter writes DateTimeOffset as DateTime.
// Our internal writers should use the DateTimeOffset-String conversion from XmlConvert.
WriteString(XmlConvert.ToString(value));
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 comment seems not relevant as even now it is using

public static string ToString(DateTimeOffset value)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe it is connected with base code ?

public virtual void WriteValue(DateTimeOffset value)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@krwq Do you know if somebody is changing this Mode ?

internal static class XmlCustomFormatter
{
private static DateTimeSerializationSection.DateTimeSerializationMode s_mode;
private static DateTimeSerializationSection.DateTimeSerializationMode Mode
{
get
{
if (s_mode == DateTimeSerializationSection.DateTimeSerializationMode.Default)
{
s_mode = DateTimeSerializationSection.DateTimeSerializationMode.Roundtrip;
}
return s_mode;
}
}

I saw similar code in XmlSerializers and the only way to change is with Reflection and directly manipulating fiedl.

WriteWithBuffer((char)value, XmlConvert.TryFormat);
break;
default:
//Guid is not supported in XmlUntypedConverter.Untyped and throws exception
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 limitation is really strange, but we need to adapt XmlValueConverter to allow Guid in it's methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@@ -1657,5 +1657,241 @@ internal static Exception CreateInvalidNameCharException(string name, int index,
{
return CreateException(index == 0 ? SR.Xml_BadStartNameChar : SR.Xml_BadNameChar, XmlException.BuildCharExceptionArgs(name, index), exceptionType, 0, index + 1);
}

public static bool TryFormat(bool value, Span<char> destination, out int charsWritten)
Copy link
Contributor

@PaulusParssinen PaulusParssinen Sep 11, 2022

Choose a reason for hiding this comment

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

I believe this one can do same optimization as in #64782 but with correct casing.

@@ -981,6 +984,117 @@ public override void WriteBinHex(byte[] buffer, int index, int count)
}
}

// Override in order to handle Xml simple typed values and to pass resolver for QName values
public override void WriteValue(object value)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Whole logic for writing with buffer is duplicated in XmlTextWriter and XmlRawWriter.
One suggestion is to create "public" class( this is needed as XmlTextWriter is public) and encapsulate logic in it.
The other is to have some static helper class which works by ref with char[].

Which one do you prefer ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Duplication logic reduced, but the question still stays open.

@TrayanZapryanov
Copy link
Contributor Author

@krwq, @eiriktsarpalis Is it possible to receive some initial feedback on these changes and to the following ones(changes in serializators to use typed methods instead of converting to string first) ?
Do you see any value in this idea or you expect some problem later that will stop it?
Should I continue with following PRs to change one by one serializers(Reflection, Primitive, IlGen....) based on this or to wait until this one is merged?
I am quite new in proposing changes and will need some guidance if possible.

@@ -185,7 +184,75 @@ public override void WriteValue(object value)
{
ArgumentNullException.ThrowIfNull(value);

WriteString(XmlUntypedConverter.Untyped.ToString(value, _resolver));
Type sourceType = value.GetType();
Copy link
Member

Choose a reason for hiding this comment

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

This seems to be adding a lot of branching where it previously didn't exist. Could this be done differently? I'm not super familiar with this code, but one thought might be to expose a TryFormat method to XmlUntypedConverter.Untyped.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The code in XmlUntypedConverter.ToString(object) is already similar:
```cs
public override string ToString(object value, IXmlNamespaceResolver? nsResolver)
{
ArgumentNullException.ThrowIfNull(value);

    Type sourceType = value.GetType();

    if (sourceType == BooleanType) return XmlConvert.ToString((bool)value);
    if (sourceType == ByteType) return XmlConvert.ToString((byte)value);
    if (sourceType == ByteArrayType) return Base64BinaryToString((byte[])value);
    if (sourceType == DateTimeType) return DateTimeToString((DateTime)value);
    if (sourceType == DateTimeOffsetType) return DateTimeOffsetToString((DateTimeOffset)value);
    if (sourceType == DecimalType) return XmlConvert.ToString((decimal)value);
    if (sourceType == DoubleType) return XmlConvert.ToString((double)value);
    if (sourceType == Int16Type) return XmlConvert.ToString((short)value);
    if (sourceType == Int32Type) return XmlConvert.ToString((int)value);
    if (sourceType == Int64Type) return XmlConvert.ToString((long)value);
    if (sourceType == SByteType) return XmlConvert.ToString((sbyte)value);
    if (sourceType == SingleType) return XmlConvert.ToString((float)value);
    if (sourceType == StringType) return ((string)value);
    if (sourceType == TimeSpanType) return DurationToString((TimeSpan)value);
    if (sourceType == UInt16Type) return XmlConvert.ToString((ushort)value);
    if (sourceType == UInt32Type) return XmlConvert.ToString((uint)value);
    if (sourceType == UInt64Type) return XmlConvert.ToString((ulong)value);
    if (IsDerivedFrom(sourceType, UriType)) return AnyUriToString((Uri)value);
    if (sourceType == XmlAtomicValueType) return ((string)((XmlAtomicValue)value).ValueAs(StringType, nsResolver));
    if (IsDerivedFrom(sourceType, XmlQualifiedNameType)) return QNameToString((XmlQualifiedName)value, nsResolver);

    return (string)ChangeTypeWildcardDestination(value, StringType, nsResolver);
}

@TrayanZapryanov
Copy link
Contributor Author

Closing this in favor of 76436

@TrayanZapryanov TrayanZapryanov deleted the xml_use_char_buffer_in_xmlwriters branch September 30, 2022 10:42
@ghost ghost locked as resolved and limited conversation to collaborators Oct 30, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Xml community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants