-
Notifications
You must be signed in to change notification settings - Fork 862
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
Update unmarshallers to use system text json. #3553
base: petesong/stj
Are you sure you want to change the base?
Conversation
c01a7d9
to
ebc898a
Compare
this looks fantastic, very excited for this change |
{ | ||
bytesRead = stream.Read(buffer, 0, buffer.Length); | ||
} | ||
if (bytesRead == 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.
should the method return here, I worry that the follow line sets the reader using the buffer
that is being returned to the pool
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.
ah, yeah good suggestion the reader would be using a buffer returned to the pool in that scenario. If I return there though, it will return to the Read()
method and the _reader
won't have a buffer to work with, and I don't want to set buffer to null. I think instead I'll just refactor a bit and add something like the following in the Read()
method
if (!hasMoreData)
{
GetMoreBytesFromStream(_stream, ref _buffer, ref _reader);
hasMoreData = _reader.Read();
if (_reader.IsFinalBlock)
ArrayPool<byte>.Shared.Return(_buffer);
}
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.
while fixing a bug in the streamingutf8 reader I decided to remove the pooling due to multiple resizings of the array. I put a justification in a comment, but basically the byte array returned by Array.Resize
isn't owned by the pool so keeping track of that didn't seem worth the Renting + copying I would have to do in order to do that
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.
flipped back to pooling
@@ -63,54 +63,58 @@ public IRequest Marshall(AddPermissionRequest publicRequest) | |||
request.HttpMethod = "POST"; | |||
|
|||
request.ResourcePath = "/"; | |||
using (MemoryStream memoryStream = new MemoryStream()) | |||
#if NETCOREAPP3_1_OR_GREATER |
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 might be stretching the scope of this change too much, but would it be possible to make the source of MemoryStream
/IBufferWriter<T>
pluggable.
I'd really love to add my own pipeline middleware that allows for injecting a RecyclableMemoryStream (which implements both MemoryStream
and IBufferWriter<T>
)
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.
My interpretation of this is we would have a property on AWSConfigs
called StreamFactory
of type IStreamFactory
defined as:
public interface IStreamFactory
{
Stream GetStream();
}
For .NET Core 3.1 and above we would default this to an instance of ArrayBufferWriterFactory:
internal class ArrayBufferWriterFactory : IStreamFactory
{
public Stream GetStream()
{
// TODO: Do something like this class to wrap the ArrayBufferWriter as a Stream. https://github.com/CommunityToolkit/dotnet/blob/c23e1cc6e918f86c589facf98f70ca122495c385/src/CommunityToolkit.HighPerformance/Streams/IBufferWriterStream%7BTWriter%7D.cs#L18
return new ArrayBufferWriter<byte>();
}
}
For older targets we would use a MemoryStream based factory:
internal class MemoryStreamFactory : IStreamFactory
{
public Stream GetStream()
{
return new MemoryStream();
}
}
In our unmarshallers where we need a stream we could get rid of the conditional check and just call AWSConfigs.StreamFactory.GetManager()
.
Then @slang25 can take a dependency on Microsoft.IO.RecyclableMemoryStream
and right his own factory and assign it to AWSConfigs.StreamFactory
.
internal class RecyclableMemoryStreamFactory : IStreamFactory
{
private static readonly RecyclableMemoryStreamManager manager = new RecyclableMemoryStreamManager();
public Stream GetStream()
{
return manager.GetStream();
}
}
Does that capture your thoughts @slang25 ?
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.
Interesting, I just took a look at the class linked that does something similar, which allows the wrapper over IBufferWriter and at first glance it doesn't seem too bad. I'm a bit worried about overriding all those methods defined in the base Stream
class though... i wonder how the bufferwriter interacts with those methods and how that then interacts with the JsonWriter
if at all, maybe I'm overthinking it. Is it as performant / behave the same? Will hold off until I hear back though on if this is what you were thinking
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.
What I'm looking for is a way to get the best perf out of the AWS SDK, so having the ability to pool these buffers that are going to copy all the bytes into memory is going to reduce memory allocations significantly.
My understanding is that IBufferWriter<T>
is the fast modern to do synchronous buffered writing, vs Stream
which is more ubiquitous in the .NET world today.
That said, I think a Stream GetStream()
signature would work, but you could additional do a type check to see if it implements a IBufferWriter<T>
, and then use the appropriate overloads. That makes it convenient for use with Streams, and has a fast path for things like RecyclableMemoryStreamManager
. To be honest, this isn't a deal breaker, I haven't ran benchmarks but I'm guessing the gains are not tremendous.
The problem with a static IStreamFactory
is that I want to be able to pool the buffers and therefore want a way to return them. So the AWSConfigs
static property would need an interface to support that for it to achieve what I'm looking for.
I was thinking about having a PipelineHandler
that could be added into the pipeline, and setting the stream on some sort of context object that passes through. That way it could own the creation/pooling of buffers, and returning them. However a static property would be more ergonomic.
231beda
to
fe0da63
Compare
…mo DB Utils refactor to enable building test project
ae62725
to
12fb049
Compare
sdk/src/Core/AWSConfigs.cs
Outdated
|
||
/// <summary> | ||
/// Key for the UseSdkCache property. | ||
/// <seealso cref="Amazon.AWSConfigs.UseSdkCache"/> | ||
/// Configures the default buffer size for the <see cref="Amazon.Runtime.Internal.Util.StreamingUtf8JsonReader"/> |
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.
Since this cref is pointing to an internal undocumented class I would remove "for the <see cref="Amazon.Runtime.Internal.Util.StreamingUtf8JsonReader"/>".
|
||
namespace Amazon.Runtime.Documents.Internal.Transform | ||
{ | ||
/// <summary> | ||
/// Dedicated <see cref="IUnmarshaller{T,R}"/> for <see cref="Document"/>. | ||
/// Dedicated <see cref="IXmlUnmarshaller{T,R}"/> for <see cref="Document"/>. |
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.
Should this be IJsonUnmarshaller
instead of IXmlUnmarshaller
?
sdk/src/Core/Amazon.Runtime/EventStreams/Internal/EventStreamUtils.cs
Outdated
Show resolved
Hide resolved
sdk/src/Core/Amazon.Runtime/Internal/Transform/UnmarshallerContext.cs
Outdated
Show resolved
Hide resolved
|
||
if (bytesRead == 0) | ||
{ | ||
ArrayPool<byte>.Shared.Return(buffer); |
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.
Side question but if we have an Exception while unmarshalling do we need a mechanism to make sure the buffer is returned?
I can't remember what happens with ArrayPool
if the shared array objects are never returned. My guess is the non returned buffer is garbage collector and ArrayPool
would eventually create a new buffer to match the demand. Does that sound right to you?
If that assumption is true then maybe it is okay to let the buffers get lost in exceptions unless it is fairly easy to make sure the buffer is returned. You could I guess have a finalizer for the class and return the buffer if it hasn't already been returned.
[TestMethod] | ||
public void HandlesUtf8BOM() | ||
{ | ||
// we can't use reflection to access the private fields of StreamingUtf8JsonReader since it is a ref struct so we have to test it this way. |
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.
You could make the methods internal
in StreamingUtf7JsonReaderand then since the unit test project has access via the
InternalsVisibleToAttributetest the handle method directly. Following this pattern you could also have tests directly on
FillBufferand
GetMoreBytesFromStream`,
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 see the value in testing FillBuffer directly, but I don't really see the value in testing GetMoreBytesFromStream
directly since this method is only called in the context of Read()
. The test where I make one Json token = defaultvalueBufferSize + 500 covers the array resizing logic, and the smaller buffer size covers the reader.BytesConsumed < buffer.Length
logic. However, I could add one additional test where the stream is exactly the size of the buffer.
So I' like to keep as many methods private as I can, but if you feel strongly about this I can change it to internal. I just don't see what additional testing I would do other than exact buffer size.
// here we're creating a json string that is greater than the default buffer size to test the GetMoreBytesFromStream logic | ||
var sb = new StringBuilder(); | ||
sb.Append("{ \"key\": \""); | ||
sb.Append(new string('x', 7500)); |
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.
In case we increase the default size but don't change the value here you might want to reference the AWSConfigs
value plus some size bigger.
/// This class just tests the wrapper class StreamingUtf8JsonReader. | ||
/// </summary> | ||
[TestClass] | ||
public class StreamingUtf8JsonReaderTests |
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 should have a test for the scenario where the PassReaderByRef
is used for converting the JsonDocument and the the document doesn't fit in the buffer.
string largeJson = sb.ToString(); | ||
|
||
byte[] payload = Encoding.UTF8.GetBytes(largeJson); | ||
using (var stream = new MemoryStream(payload)) |
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.
To help verification you might want to add an internal property on the StreamingUtf8JsonReader
for the current length of the buffer. Then you can check before and after that the buffer size increased.
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 tried to do this, but the problem is that once we hit the final block we set buffer to null and then getting the buffer's size leads to a null pointer
/// </summary> | ||
/// <param name="reader">The Utf8JsonReader</param> | ||
/// <returns></returns> | ||
public abstract bool Read(ref StreamingUtf8JsonReader reader); |
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.
Since the Xml and Json UnmarshallerContext subclasses have different read methods then we shouldn't even bother putting them on the base class. Anybody interacting with the context to do read have to work with the object as the subclass. And by putting the abstract methods here we have each one having to implement both versions with one version throwing a not implemented exception.
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.
Good point, I removed both the abstract Read()
with no parameters and abstract Read(ref streamingUtf8JsonReader reader)
and moved each one to its unmarshaller context class. I also moved ReadAtDepth(int targetDepth)
since the Json unmarshaller context's ReadAtDepth
accepts an additional argument anyways.
JsonDocument document = null; | ||
streamingReader.PassReaderByRef((ref Utf8JsonReader reader) => | ||
{ | ||
document = JsonDocument.ParseValue(ref reader); |
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.
Relying on the comment telling users to not call this unless unless the full document contents have been read will lead to hard to detect bugs. If we need this method then we should rework and rename the PassReaderByRef
method to create a new Utf8JsonReader
with the full content.
Where is this method even being called? I didn't see anywhere and if it isn't being called can we get rid of it.
} | ||
|
||
/// <summary> | ||
/// This method should be be u |
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.
Comment is cut off but I suspect the real change is to get rid of this method and make the other overload method work in all cases.
/// </summary> | ||
public static class JsonConstants | ||
{ | ||
public static ReadOnlySpan<byte> Utf8Bom => new byte[] { 0xEF, 0xBB, 0xBF }; |
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.
Oh yes, the joys of ref structs. Then split out the byte array allocation from the ReadOnlySpan Lambda. Something like this.
private static byte[] _utf8BomBytes = new byte[] { 0xEF, 0xBB, 0xBF };
public static ReadOnlySpan<byte> Utf8Bom => _utf8BomBytes;
|
||
_stream = stream; | ||
_buffer = ArrayPool<byte>.Shared.Rent(AWSConfigs.StreamingUtf8JsonReaderBufferSize ?? 4096); | ||
_options = new JsonReaderOptions |
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.
Since the options never changes can we make it a private static field of the type to avoid recreating it.
private static JsonReaderOptions _jsonOptions = new JsonReaderOptions
{
AllowTrailingCommas = true
};
SkipUtf8Bom(ref _buffer); | ||
} | ||
|
||
private void SkipUtf8Bom(ref byte[] buffer) |
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.
Realizing this method does more then just Skip the Ut8Bom it is also what is in charge of providing the initial content for the buffer I think you should just collapse this method with the constructor. That also gets rid of the awkward _reader
creating in the constructor that gets recreated here. Something like this:
public StreamingUtf8JsonReader(Stream stream)
{
if (stream is null)
throw new ArgumentException("Stream must not be null. Please initialize a stream and pass it into the constructor.");
_stream = stream;
_buffer = ArrayPool<byte>.Shared.Rent(4096);
_options = new JsonReaderOptions
{
AllowTrailingCommas = true
};
// Read the initial buffer from the stream and skip over UTF8 BOM if it exists.
int bytesRead = _stream.Read(_buffer, 0, _buffer.Length);
int start = 0;
if (_buffer.AsSpan().StartsWith(JsonConstants.Utf8Bom))
{
start += JsonConstants.Utf8Bom.Length;
bytesRead -= Utf8Bom.Length;
}
_reader = new Utf8JsonReader(_buffer.AsSpan(start, bytesRead), isFinalBlock: bytesRead == 0, new JsonReaderState(_options));
}
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's a good point about the awkward _reader
recreation. I will just collapse it at this point.
// because it was too large to fit in the remainder of the buffer. | ||
ReadOnlySpan<byte> leftover = buffer.AsSpan().Slice((int)reader.BytesConsumed); | ||
|
||
if (leftover.Length == buffer.Length) |
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.
Okay but the comment about the if
block is over the leftover
and logic about going into the if block is more about if BytesConsumed
is equal to 0. It is an extra layer of logic to understand that if BytesConsumed
is equal to 0 then leftover.Length == buffer.Length
is true. The logic flow is also hard to read in general because the conditions are spread out across the function with more variables being created that are only used sometimes. For example if you are recreating the buffer you go in the first if
block then skip some code then go in another blocking continue the resize logic if the resized
variable is true.
This is my attempt trying to keep the related logic close to each other and get rid of excess variables. I haven't test this though.
int bytesRead = 0;
if (reader.BytesConsumed < buffer.Length)
{
// If BytesConsumed is 0 then the previous reader.Read() method failed because
// the next JSON token to read from the stream is too large to fit in the buffer.
// That means we need to resize the buffer and try again to read the next JSON token.
if (reader.BytesConsumed == 0)
{
// Rent a new buffer twice the size of the previous and fill the new resized buffer with data from the stream
var resizedBuffer = ArrayPool<byte>.Shared.Rent(Math.Min(int.MaxValue, (buffer.Length * 2)));
bytesRead = FillBuffer(stream, ref resizedBuffer, 0, resizedBuffer.Length);
// Recreate ref parameter reader using a span of the data from the new resized buffer.
var resizedSpan = resizedBuffer.AsSpan(0, bytesRead + buffer.Length);
reader = new Utf8JsonReader(resizedSpan, isFinalBlock: bytesRead == 0, reader.CurrentState);
// Return old buffer and update the ref buffer parameter to new resized buffer.
Logger.GetLogger(typeof(StreamingUtf8JsonReader)).DebugFormat("Resizing buffer from {0} to {1}", buffer.Length, resizedBuffer.Length);
ArrayPool<byte>.Shared.Return(buffer);
buffer = resizedBuffer;
// Do an early return since we have a new Utf8JsonReader with a filled buffer.
return;
}
else
{
// Move the unprocessed data from the buffer to the start and the fill
// remaining space in the buffer with new content from the stream.
ReadOnlySpan<byte> leftover = buffer.AsSpan().Slice((int)reader.BytesConsumed);
leftover.CopyTo(buffer);
bytesRead = FillBuffer(stream, ref buffer, leftover.Length, buffer.Length - leftover.Length);
}
}
else
{
bytesRead = FillBuffer(stream, ref buffer, 0, buffer.Length);
}
…plit out more methods in XmlUnmarshallerContext and JsonUnmarshallerContext
I've addressed your feedback, thanks for the detailed look. I updated the logic in StreamingUtf8JsonReader as well so that it is easier to follow |
/// <returns> | ||
/// The text contents of the current token being parsed. | ||
/// </returns> | ||
public override string ReadText() |
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.
See if you have to have this method defined on the base class. Any time we have code like this check to see if the methods should really be on the base 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.
Oh good catch. This one I just forgot to remove. Since the method signatures are different, I removed it from UnmarshallerContext
.
|
||
_stream = stream; | ||
_buffer = ArrayPool<byte>.Shared.Rent(AWSConfigs.StreamingUtf8JsonReaderBufferSize ?? 4096); | ||
// need to initialize the reader even if the buffer is empty because auto-default of unassigned fields is only |
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.
Delete this orphan comment.
ArrayPool<byte>.Shared.Return(buffer); | ||
buffer = resizedBuffer; | ||
bytesRead = FillBuffer(stream, ref buffer, leftover.Length, buffer.Length - leftover.Length); | ||
var resizedSpan = buffer.AsSpan(0, bytesRead + previousBufferLength); |
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.
Again this is a new buffer empty buffer we filled up with previous FillBuffer
call. The span should be from 0 to bytesRead
. We don't care about the previousBufferLength
.
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 explain in the comment above. however, I will remove previousBufferLength
because that is just leftover.Length
in this case.
…sary abstract method on base unmarshallercontext class
Based on our conversation internally I made some updates |
bytesRead = FillBuffer(stream, ref buffer, 0, buffer.Length); | ||
} | ||
|
||
if (bytesRead == 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.
I'm thinking this is getting complicated because we are depending on if the code goes into the else
block up above dealing with leftover data we also expect this code to handle how to create the reader. What do you think of this version where whenever we go into any condition block it does everything it needs to do and not rely on logic somewhere else to finish things up.
I also updated how the Utf8JsonReader
was created in the resize condition to say if it is the final block if we did not fill up the entire new buffer.
private static void GetMoreBytesFromStream(Stream stream, ref byte[] buffer, ref Utf8JsonReader reader)
{
if (reader.BytesConsumed < buffer.Length)
{
ReadOnlySpan<byte> leftover = buffer.AsSpan().Slice((int)reader.BytesConsumed);
// If BytesConsumed is 0 that means that the previous Read failed because the JSON token was too large to fit in the buffer.
// In that case we need to resize the buffer and try again to read the JSON token.
if (reader.BytesConsumed == 0)
{
var resizedBuffer = ArrayPool<byte>.Shared.Rent(Math.Min(int.MaxValue, (buffer.Length * 2)));
Logger.GetLogger(typeof(StreamingUtf8JsonReader)).DebugFormat("Resizing buffer from {0} to {1}", buffer.Length, resizedBuffer.Length);
// copy over the data from the previous read's buffer to the newly resized buffer.
buffer.AsSpan().CopyTo(resizedBuffer);
// return the previous buffer to the pool and set the new buffer to equal the resized buffer.
ArrayPool<byte>.Shared.Return(buffer);
buffer = resizedBuffer;
// fill the new resized buffer with data from the stream. the offset MUST be leftover.Length
// so we don't overwrite the data that was copied from the previous buffer, and the number of bytes
// we read must be buffer.Length - leftover.Length which is just the second half of the buffer.
var bytesRead = FillBuffer(stream, ref buffer, leftover.Length, buffer.Length - leftover.Length);
var resizedSpan = buffer.AsSpan(0, bytesRead + leftover.Length);
reader = new Utf8JsonReader(resizedSpan, isFinalBlock: bytesRead == 0 || resizedSpan.Length != resizedBuffer.Length, reader.CurrentState);
// early return since we have a reader
return;
}
// The buffer has had some data processed but there is leftover data unprocessed.
// In this case we move the unprocessed data to the start of the buffer and fill the rest with new data.
else
{
leftover.CopyTo(buffer);
var bytesRead = FillBuffer(stream, ref buffer, leftover.Length, buffer.Length - leftover.Length);
reader = new Utf8JsonReader(buffer.AsSpan(0, bytesRead + leftover.Length), isFinalBlock: ((bytesRead + leftover.Length) != buffer.Length || bytesRead == 0), reader.CurrentState);
return;
}
}
// In this case entire buffer has been processed so attempt to refill the entire
// buffer.
else
{
var bytesRead = FillBuffer(stream, ref buffer, 0, buffer.Length);
reader = new Utf8JsonReader(buffer.AsSpan(0, bytesRead), isFinalBlock: bytesRead == 0 || bytesRead != buffer.Length, reader.CurrentState);
return;
}
}
public static void Initialize(TestContext testContext) | ||
{ | ||
originalBufferSize = AWSConfigs.StreamingUtf8JsonReaderBufferSize.GetValueOrDefault(); | ||
AWSConfigs.StreamingUtf8JsonReaderBufferSize = 4096; |
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 would recommend adding a second constructor on the StreamingUtf8JsonReader
that takes in the buffer size so you don't have to toggle the AWSConfigs.StreamingUtf8JsonReaderBufferSize
global setting.
Description
Updates the unmarshallers to use System Text Json for deserialization.
918fa40 - Core and generator changes along with refactoring of DynamoDB Utils class to get test project to compile
3f84a2c - manual S3 changes for unmarshallers
12fb049 - generated changes for SQS as an example
53984c3 - fixing unnecessary padding
Some things I want to point out.
Utf8JsonReader
cannot accept a stream in its constructor, I created a wrapper aroundUtf8JsonReader
that accepts a stream and fills the buffer to pass into the reader. Since the wrapper is also a ref struct it can holdUtf8JsonReader
as a field.IUnmarshaller
=>IXmlUnmarshaller
andIJsonUnmarshaller
. This gives the added benefit that each structure unmarshaller no longer throwsNotImplementedException
for the other protocol.This is necessary for when we add new protocols down the line. We don't want each structure unmarshaller to implement all protocol interfaces just to throw
NotImplemented
for the others.Motivation and Context
Testing
I tested a couple real API calls. I added unit tests for the streamingUtf8JsonReader, and all the protocol tests pass.
Full dry run will be done in the feature branch.
Screenshots (if appropriate)
Overall, I saw around a 77% performance gain in unmarshalling (note this is on my machine not an EC2 instance). I will attach specific numbers from an ec2 instance as a follow up.
Types of changes
Checklist
License