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
C# - support byte[]-segment deserializers **without** changing the calling API #19460
Conversation
… API; use shared buffer-pool to implement leased buffers
/// Only the range defined by Offset and Count shoud be accessed by the consumer. | ||
/// </summary> | ||
/// <returns>a leased buffer containing the entire payload</returns> | ||
public virtual LeasedBuffer PayloadAsLeasedBuffer() |
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 experimented with similar concept in the past ( PayloadAsRentedBuffer() ) which was using ArrayPool.Shared.Rent internally, but I decided not to add that method as I wasn't sure it was providing much extra value.
Once you're on netstandard2.0 (= GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY is true), the can provide a deserializer that can easily perform pooling (custom or using ArrayPool) and read the content from PayloadAsReadOnlySequence(). This doesn't help .NET desktop users, but my assumption was is that most users that would care about performance would already be using .NET core.
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 don't think that final assumption is valid: many folks care deeply but are still attempting gradual migration of large code-bases. But it may be moot - see other comment
/// Only the range defined by Offset and Count shoud be accessed by the consumer. | ||
/// </summary> | ||
/// <returns>a leased buffer containing the entire payload</returns> | ||
public virtual LeasedBuffer PayloadAsLeasedBuffer() |
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.
Btw, any reason for not using IMemoryOwner instead of defining a custom type?
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.
IMemoryOwner doesn't expose an array, but: it is probably not important as I think we can lose this API completely given the context I now understand about ROS-byte
the problem is that for Span<> you need C# 7.2 and we do have users that only support C# 6, so for now we stayed safe and only opted for the System.Memory dependency and Span<> when on a platform that definitely has support for C# 7.2 (nestandard2.0 in our case) |
/// Given a deserializer of the form foo.Bar(byte[]), see if we can find a similar foo.Bar(byte[], int, int); if so, | ||
/// prefer that; this allows the existing protoc code-gen to be identified to use pooled buffers | ||
/// </summary> | ||
private static bool TryFindArraySegmentDeserializer(Func<byte[], T> deserializer, out Func<DeserializationContext, T> contextualDeserializer) |
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.
+1 to the idea of trying to make use the foo.Bar(byte[], int, int)
parser without requiring any codegen changes,
I think that is something that's worth investigating. But we also need to give this a bit more thought so that it doesn't have any unwanted consequences (e.g. protobuf is not the only serialization format supported and we don't know what this code would do when other serialization formats are being 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.
We could perhaps further restrict it to where the first parameter matches name and the second parameters are named offset
and count
? I think that's pretty unambiguous about the intent...
That would be a legitimate reason for not emitting code to consume this API in protoc (without an opt-in), but it doesn't really impact the library if there's an API that some consumers can't use fully (they can actually still use a lot of it, though). In particular, the framework and language are completely separate and independent: you can be targeting either from either, just about. So: if that's the history, can I suggest an easier solution here that would allow me to remove almost all of these changes?
Thoughts? |
Oof, except maybe xamarin. Dammit xamarin, y u so hard! But: if xamarin targets netstandard1.*, Spans could still be enabled on .net framework. Edit: wait, I might be thinking of unity. Xamarin might be fine after all! One of them is .. awkward |
Unity is the one that's awkward. They are only providing netstandard2.0 and C# 7.2 support since very recently (the current "package" for unity targets net45)) and there's issues with things like reflection. |
@jtattermusch so I'm guessing that unity is using the netstandard1.5 build? in which case, would you be open to extending Going back to your earlier point about folks who are interested in perf: I do entirely agree that those folks aren't targeting netstandard1.5; they might still be stuck on .NET Framework, though. And: a .NET Framework 4.7.2 application targeting a lib that exposes I can be sold on the idea that trying to expose it in netstandard1.5 helps almost nobody, so if not doing that helps with unity etc, then; fine! |
…S, on the basis that we can do that with net45 and ns20 and cover almost everyone
@jtattermusch I've pushed the simplified version of the code, which assumes we can extend edit: I got the |
Actually right now Unity uses the net45 assembly: grpc/src/csharp/build_unitypackage.bat Line 46 in da3784b
We might be able to switch unity over to netstandard2.0, but I haven't tried that yet and it would also mean that users would only be able use Unity versions starting from 2018.3 https://docs.unity3d.com/2018.3/Documentation/Manual/dotnetProfileSupport.html
|
The problem with supporting Span<> based APIs in net45 profile is that users might be on a system that doesn't support C# 7.2 and even thoug framework and compiler used are two separated things, on C# 6 systems the Also, if Span is used internally in a library build using C# 7.2 (Grpc.Core in our case), can that codepath be safely executed on an old system (that has no knowledge of C# 7+ or ref structs)? If we address these questions sufficiently, I'll be more than happy to add the Span<> based optimizations to the net45 build too. |
@jtattermusch I understand the details of how I did not propose any API changes that added spans etc onto As for old systems: The transpiling question is a good one. It would be good to understand what targets we're worried about there, so that it can be investigated. |
It might make sense to split this PR into two independent parts:
|
sure, I can do that |
} | ||
|
||
#if NET45 | ||
private static readonly byte[] s_empty = new byte[0]; // Array.Empty not available on all target platforms |
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.
nit: Is it really worth adding #if NET45
(which is not great for readability) as opposed to just using a static member always?
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.
fixed next push
#if NET45 | ||
private static readonly byte[] s_empty = new byte[0]; // Array.Empty not available on all target platforms | ||
#endif | ||
private static readonly Type[] s_arraySegmentSignature = new Type[] { typeof(byte[]), typeof(int), typeof(int) }; |
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.
nit: in the Grpc codebase we are currently not using the s_
prefix for static readonly members (we use capital).
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.
fixed next push
@@ -100,6 +100,7 @@ | |||
|
|||
<ItemGroup> | |||
<PackageReference Include="System.Interactive.Async" Version="3.2.0" /> | |||
<PackageReference Include="System.Buffers" Version="4.5.0" /> |
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.
nit: System.Memory brings System.Buffers transitively, it it worth adding it explicitly?
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.
that was a hangover from when I wasn't using System.Memory for this; no longer needed, removing
@jtattermusch nits and csproj-split: done |
Light ping on this PR - for large buffer deserialization I'd find the latest version of this very useful to have in the API to help avoid copies - possible to complete this? |
This issue/PR has been automatically marked as stale because it has not had any update (including commits, comments, labels, milestones, etc) for 180 days. It will be closed automatically if no further update occurs in 1 day. Thank you for your contributions! |
Looks like this is no longer relevant now that #23485 and corresponding Protobuf improvements |
Agreed, thanks
…On Mon, 19 Oct 2020, 10:04 Jan Tattermusch, ***@***.***> wrote:
Looks like this is no longer relevant now that #23485
<#23485> and corresponding Protobuf
improvements
protocolbuffers/protobuf#7576
<protocolbuffers/protobuf#7576> and
protocolbuffers/protobuf#7351
<protocolbuffers/protobuf#7351> are in place?
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#19460 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAEHMH2HEXMQKJHT6KTN53SLP6JZANCNFSM4H3GNZXA>
.
|
Incomplete: still working on getting the tests working locally; dropping this here to show direction
First of several steps intended to reduce allocations when deserializing and serializing.
Scope: this set of changes focuses on reducing allocations during deserialization over all supported platforms using the existing generated code, i.e. no protoc changes. This is achieved by:
LeasedBuffer
conceptFunc<byte[], T>
, automatically detect and switch over to aFunc<byte[],int,int,T>
, making use of the new leased buffer if a match is foundThis still involves a copy, so isn't perfect, but it is far less allocatey
Future ideas:
ReadOnlyMemory<byte>
(zero copy) when it is a single range (this is a pretty common case, and easy to optimize for); presumablyTryGetMemory
or similar?Stream
ReadOnlySequence<byte>
in more platforms (it should work, etc)