-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Remove XmlConvert.StrEqual and use Span<char>.StartsWith() instead. #74955
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
Remove XmlConvert.StrEqual and use Span<char>.StartsWith() instead. #74955
Conversation
Tagging subscribers to this area: @dotnet/area-system-xml Issue DetailsReplace loop with Span.SequenceEqual. On my machine following benchmark:
produces following results:
|
Replace with StartsWith as suggested.
I think changes are ready for review. Failing tests are not related with changes. |
src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImpl.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImpl.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImplHelpers.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImplHelpers.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks good. Do we have tests that appropriately exercise these code paths? e.g. If you do a code coverage run, are all of these hit?
@stephentoub If possible to ask something not related to this PR? Here it is how my table looks like :
|
inside of the relevant test directory. Unfortunately the System.Private.Xml tests are a bit of a mess and are spread out over many test projects (I'd really like to see us consolidate them all into a single project, but that's not your problem 😄). @krwq, which is the right test project for this functionality?
I'd need to profile it to see where the time is being spent :) But for starters in the case of it needing to add to the table, it's doing two lookups, one in TryGetValue and one in Add; I'm not sure what your test is actually exercising, but if it's the add path, I'd start there and find a way to reduce that to a single lookup rather than two. |
I've check code coverage with System.Private.Xml\tests\XmlSerializer and System.Private.Xml\tests\XmlDocument and was able to find most of code paths covered, but for example changes in XmlTextReaderImpl.IncrementalRead are not executed there. |
Thanks for checking. Is it possible to add tests that ensures they are covered? |
I've tried to throw exception in runtime/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImpl.cs Line 8225 in cb1343d
and then to run all tests in System.Private.Xml, but none of them failed. Looks like this is not covered or maybe some other module is using it. Unfortunately I am not so deep in the code to understand use case of it. If possible somebody to give me a hint of sample xml , and I will create tests for it. I suspect that this is something connected with binary data inside xml, or DataContract serializer is using it. |
@stephentoub First of all - thanks for consolidating xml tests. runtime/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImpl.cs Line 8190 in 8de1809
I will be glad to add tests, just need help from somebody to give me hint - when it is expected this method to be used. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes LGTM. Thank you for your contribution! Couple of remarks:
- Would it be possible to re-run your benchmark, just to validate that perf gains are preserved after review feedback changes?
- Not very familiar with the codebase to suggest improvements to code coverage, perhaps @krwq can help. But I think we can merge this change without that.
I've tried all benchmarks with Xml in it's name from performance repo and here it is the result: StatisticsTotal: 69 Statistics per Architecture
Statistics per Operating System
Statistics per Namespace
System.Xml.Linq.Perf_XElement.GetValue
MicroBenchmarks.Serializers.Xml_FromStream.DataContractSerializer_
MicroBenchmarks.Serializers.Xml_FromStream.XmlSerializer_
MicroBenchmarks.Serializers.Xml_FromStream.DataContractSerializer_
MicroBenchmarks.Serializers.Xml_ToStream.DataContractSerializer_
MicroBenchmarks.Serializers.Xml_FromStream.XmlSerializer_
MicroBenchmarks.Serializers.Xml_FromStream.XmlSerializer_
MicroBenchmarks.Serializers.Xml_ToStream.DataContractSerializer_
Microsoft.Extensions.Configuration.Xml.XmlConfigurationProviderBenchmarks.Load(FileName: "names.xml")
MicroBenchmarks.Serializers.Xml_FromStream.XmlSerializer_
MicroBenchmarks.Serializers.Xml_ToStream.DataContractSerializer_
System.Xml.Linq.Perf_XElementList.Enumerator
MicroBenchmarks.Serializers.Xml_ToStream.DataContractSerializer_
MicroBenchmarks.Serializers.Xml_FromStream.XmlSerializer_
MicroBenchmarks.Serializers.Xml_FromStream.XmlSerializer_
Microsoft.Extensions.Configuration.Xml.XmlConfigurationProviderBenchmarks.Load(FileName: "repeated.xml")
MicroBenchmarks.Serializers.Xml_FromStream.XmlSerializer_
|
Thanks, I think this looks good. @stephentoub any objection to merging without added coverage? |
src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImpl.cs
Outdated
Show resolved
Hide resolved
How do we know it didn't break something? |
If you start from https://source.dot.net/#q=incrementalread and click on IncrementalRead in the left list, that will bring you to the source for that method. Then click on the method name in the code pane on the right, and the pane on the left will show you all the call sites. You can then click on one of those to navigate the right pane to that usage, and continue that process to look for all the ways to get to this function. |
@eiriktsarpalis, @stephentoub Now: |
} | ||
|
||
private static string GenerateTestXml(out string expected) | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider just using const and and possibly string.Replace for line ending
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've found this way of generating test xmls in here:
public static void CreateGenericTestFile(string strFileName) |
If you like - I can change it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this code base is old and ugly, let's at least make new code prettier since we don't have time on improving the entire code base
src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImpl.cs
Show resolved
Hide resolved
// ParseQName can flush the buffer, so we need to update the startPos, pos and chars after calling it | ||
int endPos = ParseQName(true, 1, out _); | ||
if (XmlConvert.StrEqual(_ps.chars, _ps.charPos + 1, endPos - _ps.charPos - 1, _curNode.localName) && | ||
if (endPos - _ps.charPos - 1 == _curNode.localName.Length && _ps.chars.AsSpan(_ps.charPos + 1).StartsWith(_curNode.localName) && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not slice exact length (endPos - _ps.charPos - 1
) and check with equals rather than doing check separately?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Thanks @TrayanZapryanov! |
Replace loop with Span.StartsWith.
On my machine following benchmark:
produces following results:
BenchmarkDotNet=v0.13.2, OS=Windows 10 (10.0.19043.1889/21H1/May2021Update)
11th Gen Intel Core i9-11900K 3.50GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=7.0.100-preview.2.22153.17
[Host] : .NET 6.0.8 (6.0.822.36306), X64 RyuJIT AVX2
DefaultJob : .NET 6.0.8 (6.0.822.36306), X64 RyuJIT AVX2