Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Add datetime read span path for netcore #31044

Merged
merged 18 commits into from
Oct 4, 2018
Merged

Conversation

Wraith2
Copy link
Contributor

@Wraith2 Wraith2 commented Jul 13, 2018

Second attempt at the PR with a clean branch and including all feedback from the original at corefx/30371

Locally I'm getting a problem with the allconfigurations build not finding mscorlib but I can't fathom why my changes would be responsible for that so I'm hoping CI doesn't have the same issue.

fixes https://github.com/dotnet/corefx/issues/28270

/cc @EgorBo @afsanehr @cod7alex

@Wraith2 Wraith2 changed the title [WIP] add datetime read span path for netcore Add datetime read span path for netcore Jul 13, 2018
@AfsanehR-zz AfsanehR-zz self-requested a review July 17, 2018 00:42
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

Copy link

Choose a reason for hiding this comment

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

can you please remove line 5- line 9

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, they were copied from original non-netcore file so perhaps some cleanup elsewhere might be a good idea.

@@ -18,6 +18,8 @@
</ItemGroup>
<ItemGroup Condition="'$(IsPartialFacadeAssembly)' != 'true' AND '$(OSGroup)' != 'AnyOS' AND '$(TargetGroup)' == 'netcoreapp'">
<Compile Include="System\Data\SqlClient\PoolBlockingPeriod.cs" />
<Compile Include="System\Data\SqlClient\SqlBuffer.NetCoreApp.cs" />
Copy link

Choose a reason for hiding this comment

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

change to lowercase netcoreapp

Copy link

Choose a reason for hiding this comment

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

why are we spliting the implementation into different files as netfx and netstandard dont use this implementation ?

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 followed the existing use of mixed case in the project, I can make it lower for this one but it won't be consistent throughout.

It was split apart at request of @afsanehr in the original review on #30371

Copy link

Choose a reason for hiding this comment

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

we are using the lowercase netcoreapp everywhere else in the repo when it is appearing as an extension.

Copy link
Contributor

Choose a reason for hiding this comment

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

@Anipik The reason I used .NetCoreApp in #30371 is because we previously had this file in SqlClient named as .NetCoreApp. Do you propose changing this file as well?

Copy link
Member

Choose a reason for hiding this comment

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

We are pretty inconsistent about using the .netcoreapp.cs suffix for implementation files that are conditionalized with '$(TargetGroup)' == 'netcoreapp'. The pattern is mainly for test files, since those are more often shared with .NET Framework. Personally I wouldn't use the suffix in implementation files but I don't think it matters although hopefully it's consistent within a project.
Casing should be lower case.

@@ -1109,7 +1109,11 @@ internal void SetToDateTimeOffset(DateTimeOffset dateTimeOffset, byte scale)
_isNull = false;
}

#if netcoreapp
private static void FillInTimeInfo(ref TimeInfo timeInfo, ReadOnlySpan<byte> timeBytes, int length, byte scale)
Copy link

Choose a reason for hiding this comment

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

why cant we just keep the span version ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Only the netcore build has a dependency on System.Memory which is required for span. Rather than force all netfx and netcore consumers to pull in that and possibly a full set of interdependent assemblies supporting it I talked with @karelz in gitter and he suggested making it netcore specific. If someone wants to make the decision to require span for all versions then everyone can use the span implementation.

Copy link

Choose a reason for hiding this comment

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

The netfx wont use this implementation. AFAIK the implementatation in the src folder is only built and compiled for netcoreapp which makes the if def redundant.

@danmosemsft am i missing something here ?

Copy link
Member

Choose a reason for hiding this comment

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

@stephentoub thoughts about this pattern?
We try to avoid #if platform in our implementation.

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'm definitely missing something here. The conditions for inclusion of SqlBuffer and TdsStateParserObject in the project file are simply Condition="'$(IsPartialFacadeAssembly)' != 'true' AND '$(OSGroup)' != 'AnyOS'" You assert that netfx won't use this, how do you know this and where can I determine this? If this is the case then surely it still needs to build with build -allconfigurations because, again in the original PR I referenced, it didn't and I was asked to change it to pass the CI.

If neither code level #define's or conditionally included *.netcoreapp.cs files are appropriate to separate out particular functionality just how should it be done? How should this change be made without forcing a dependency on System.Memory on all consumers?

Copy link
Member

Choose a reason for hiding this comment

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

This source code is going to be compiled against netstandard, netcoreapp, uap and netfx, however for netfx this will be a partial facade assembly and none of the source files are included for netfx. We know this because IsPartialFacadeAssembly is set to true when targeting netfx:

<IsPartialFacadeAssembly Condition="'$(TargetsNetFx)' == 'true'">true</IsPartialFacadeAssembly>

And System.Memory is inbox in Uap and Netcoreapp so there shouldn't be a problem on adding a reference to it:

<IsNETCoreApp>true</IsNETCoreApp>
<IsUAP>true</IsUAP>

System.Memory is already referenced in this project when TargetsWindows == true, so it won't hurt moving this to when IsPartialFacade != true and AnyOS != true. For netstandard it doesn't hurt anything to add this dependency, as System.Data.SqlClient in netstandard is an OOB package.

Copy link
Member

Choose a reason for hiding this comment

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

This source code is going to be compiled against netstandard, netcoreapp, uap and netfx, however for netfx this will be a partial facade assembly and none of the source files are included for netfx.

Agreed.

And System.Memory is inbox in Uap and Netcoreapp so there shouldn't be a problem on adding a reference to it

That is correct.

@@ -4540,9 +4540,15 @@ internal bool TryReadSqlValue(SqlBuffer value, SqlMetaDataPriv md, int length, T

private bool TryReadSqlDateTime(SqlBuffer value, byte tdsType, int length, byte scale, TdsParserStateObject stateObj)
{
#if netcoreapp
Span<byte> datetimeBuffer = length <= 10 ? stackalloc byte[length] : new byte[length];
Copy link

Choose a reason for hiding this comment

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

why not just the span version ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

System.Memory is only included in specific builds.

}

AssertValidState();

Copy link

Choose a reason for hiding this comment

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

nit extra line

@karelz karelz added this to the 3.0 milestone Jul 19, 2018
@Wraith2
Copy link
Contributor Author

Wraith2 commented Aug 8, 2018

Is more work needed on this to get it merged?

@Anipik
Copy link

Anipik commented Aug 8, 2018

@stephentoub @safern can you take a final look ?

@Wraith2
Copy link
Contributor Author

Wraith2 commented Aug 12, 2018

General spannification style questions.
Converting a method signature from bool TryReadByteArray(byte[] buff, int offset, int len) to bool TryReadByteArray(Span<byte> buff, int len),
At call sites prefer extension buff.AsSpan(offset) or explicit span creation new Span(buff, offset).
At call sites where offset is 0 prefer explicit 0 slice (or new as above) or drop the 0 parameter and make it implicit?

@karelz
Copy link
Member

karelz commented Aug 15, 2018

@safern can you please help push it forward?
@keeratsingh @afsanehr @David-Engel can you please review it too?

@safern
Copy link
Member

safern commented Aug 15, 2018

I'm waiting for this comment to be addressed: #31044 (comment)

@ahsonkhan could you help with the span question?

@ahsonkhan
Copy link
Member

ahsonkhan commented Aug 15, 2018

I'm waiting for this comment to be addressed

Is more work needed on this to get it merged?

You do not need to add netcoreapp specific ifdefs here at all. The only change required is changing the existing internal APIs to take Span<byte> instead of arrays, and potentially remove the offset/length arguments (where possible). You should use Slice/AsSpan to enforce the bounds checks (and simplify some Debug.Asserts).

See these commits as an example of what could be done to "spanify" the implementation: 9f6ace6 & 6ae0ffd(which will build successfully for all configurations). Feel free to use this as a starting point and remove offsets/lengths from the callers, however much is feasible.

General spannification style questions.
Converting a method signature from bool TryReadByteArray(byte[] buff, int offset, int len) to bool TryReadByteArray(Span<byte> buff, int len),
At call sites prefer extension buff.AsSpan(offset) or explicit span creation new Span(buff, offset).
At call sites where offset is 0 prefer explicit 0 slice (or new as above) or drop the 0 parameter and make it implicit?

  • Using AsSpan() or creating a Span using the new keyword should have no difference. If you want to chain multiple calls together, or passing it as an argument, the AsSpan() API is cleaner.
  • Slicing with offset 0 should be OK (since the operation is fairly cheap). If this is a really performance critical code path, you can add an if check and avoid slicing. But unless benchmarks show otherwise, I would keep it simple. Do you have performance numbers showing before and after? Looking at https://github.com/dotnet/corefx/issues/28270#issuecomment-395999251, the results are in milliseconds, so optimizing on when to Slice (which is on the order of nanoseconds) shouldn't matter.
  • In some cases, you should be able to encapsulate offset & length into a span slice (not just offset).

Feel free to ask additional questions, and I will help as best I can :)

public bool TryReadByteArray(Span<byte> buff, int len)
{
int ignored;
return TryReadByteArray(buff, len, out ignored);
Copy link
Member

Choose a reason for hiding this comment

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

nit, can be changed to:

return TryReadByteArray(buff, len, out _);

Or even:

return TryReadByteArray(buff.Slice(0, len), out _);

@Wraith2
Copy link
Contributor Author

Wraith2 commented Aug 24, 2018

All addressed, it still needs signoff from @GrabYourPitchforks on the span security changes and @afsanehr to review.

@ahsonkhan
Copy link
Member

@Wraith2, since this is a performance improvement (fixing System.Data.SqlClient.TdsParser performance improvement #28270), do you have performance measurements showing the improvement?

@Wraith2
Copy link
Contributor Author

Wraith2 commented Aug 24, 2018

I did have on the original PR. The intent was to remove frequent byte[] allocations and it does that so heap allocs on reading datatime's now don't happen. Any cpu perf would be entirely accidental.

Found them, on the references issue: https://github.com/dotnet/corefx/issues/28270#issuecomment-395999251

@danmoseley
Copy link
Member

@GrabYourPitchforks @afsanehr can you please take a look so we can get this finished off.

@karelz
Copy link
Member

karelz commented Sep 5, 2018

@afsanehr can you please merge the PR if there is nothing that needs further scrutiny? Thanks!

@AfsanehR-zz
Copy link
Contributor

Sure, waiting for @GrabYourPitchforks to approve the changes.

@Wraith2
Copy link
Contributor Author

Wraith2 commented Sep 13, 2018

Can anyone in the geographic locale of @GrabYourPitchforks organise a search party?

@GrabYourPitchforks
Copy link
Member

/me puts his ears back and hisses as the search party enters his cave, interrupting his hibernation.

@AfsanehR-zz
Copy link
Contributor

AfsanehR-zz commented Sep 13, 2018

@Wraith2 I am going to hold off merging this pr for now as I noticed some failures in ManualTests in SqlClient. One of them being SplitPacketTest. Could you try the following command to build the test and see if you see the same failure?

pushd C:\Users\Documents\corefx\bin\tests\System.Data.SqlClient.ManualTesting.Tests\netcoreapp-Windows_NT-Debug-x64\ 
call C:\Users\Documents\corefx\bin\testhost\netcoreapp-Windows_NT-Debug-x64\\dotnet.exe xunit.console.netcore.exe System.Data.SqlClient.ManualTesting.Tests.dll  -xml testResults.xml -notrait Benchmark=true -notrait category=nonnetcoreapptests -notrait category=nonwindowstests -class System.Data.SqlClient.ManualTesting.Tests.SplitPacketTest  -notrait category=OuterLoop -notrait category=failing
  popd

@Wraith2
Copy link
Contributor Author

Wraith2 commented Sep 14, 2018

Yes, I can replicate those test failures. I've no way to debug them though since I can only run tests through the commandline, there's simply too much code to start trying to trace it all the way from the network layer that way.

@AfsanehR-zz
Copy link
Contributor

@Wraith2 Try using recommendation for debugging in here

I suspect the issue might be coming from this line:

payloadOffset = payload[offset++] << 8 | payload[offset++];

where payloadLength is:

byte[] payload = new byte[_physicalStateObj._inBytesPacket];

@karelz
Copy link
Member

karelz commented Oct 2, 2018

Any update? Is it still blocked on test failures? I see CI all green.

_isNull = false;
}

internal void SetToTime(TimeSpan timeSpan, byte scale)
internal void SetToDateTime2(ReadOnlySpan<byte> bytes, byte scale)
Copy link
Member

Choose a reason for hiding this comment

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

What does the 2 mean in this method name? It's not very descriptive.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It means that it's using the data layout for an sql type SQLDATETIME2, which represents a datetime2 and I agree that it's a silly name but that's what it's called.

Copy link
Member

Choose a reason for hiding this comment

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

Ugh. Ok, thanks.

@Wraith2
Copy link
Contributor Author

Wraith2 commented Oct 2, 2018

      Any update? Is it still blocked on test failures? I see CI all green.

Still blocked on the manual tests. I've got to find a way to work out why a test is failing without being able to sensibly debug it and the network io being split into individual bytes. After that I will be attempting to juggle flaming chainsaws.

@Wraith2
Copy link
Contributor Author

Wraith2 commented Oct 3, 2018

The span translation of TryReadByteArray wasn't using handling offsets correctly which only surfaced when the manual tests fragmented the input stream so that full reads weren't possible. This is fixed, the standard and manual tests now pass. @afsanehr can you please review?

{
Buffer.BlockCopy(_inBuff, _inBytesUsed, buff, offset + totalRead, bytesToRead);
var copyFrom = new ReadOnlySpan<byte>(_inBuff, _inBytesUsed, bytesToRead);
var copyTo = buff.Slice(totalRead, bytesToRead);
Copy link
Member

Choose a reason for hiding this comment

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

nit: don't use var here since the right-hand side doesn't indicate the type explicitly.

@AfsanehR-zz
Copy link
Contributor

@Wraith2 Thanks! I can also confirm the manual tests are passing now. Once the comment from @ahsonkhan is addressed we are good to merge this pr. 🎉

@Wraith2
Copy link
Contributor Author

Wraith2 commented Oct 4, 2018

Done.

@AfsanehR-zz AfsanehR-zz merged commit 10ccbc1 into dotnet:master Oct 4, 2018
@Wraith2 Wraith2 deleted the sqldatespan2 branch October 4, 2018 23:55
JRahnama pushed a commit to JRahnama/SqlClient that referenced this pull request Sep 5, 2019
Port PR 31044

Relevant description of problem

For each call of the method we allocate the array of bytes `( byte[] datetimeBuffer = new byte[length];)`
instead of allocating it once and then reuse existing array.

Let's imagine that we read 1 million DateTime values from database. Using existing implementation of TdsParser we will have 1 million allocations of byte[] what causes unnecessary memory traffic and isn't so good for GC.

Related Link
dotnet/corefx#31044
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
Add datetime read span path for netcore

Commit migrated from dotnet/corefx@10ccbc1
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

System.Data.SqlClient.TdsParser performance improvement