You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I was recently doing some improvement on my own HttpClient usage in libraries of our design after reading through https://xaml.dev/post/removing-memory-allocations-in-http-requests-using-arraypools, and someone on our team asked if we could improve allocation when utilizing this library. We are using this library to communicate with various FHIR endpoints, but we're finding there's a lot of allocation that seems that it could be optimized.
So, I've created a netstandard2.0 compatible version of LimitArrayPoolWriteStream that we use for this purpose (see article, we adapted this for use in netstandard2 which might be applicable here).
internalsealedclassLimitArrayPoolWriteStream:Stream{privateconstintInitialLength=256;privatereadonlyint_maxBufferSize;privatebyte[]_buffer;privateint_length;publicLimitArrayPoolWriteStream(intmaxBufferSize):this(maxBufferSize,InitialLength){}publicLimitArrayPoolWriteStream(intmaxBufferSize,longcapacity){if(capacity<InitialLength){capacity=InitialLength;}elseif(capacity>maxBufferSize){throwCreateOverCapacityException(maxBufferSize);}_maxBufferSize=maxBufferSize;_buffer=ArrayPool<byte>.Shared.Rent((int)capacity);}protectedoverridevoidDispose(booldisposing){Debug.Assert(_buffer!=null);ArrayPool<byte>.Shared.Return(_buffer);_buffer=null!;base.Dispose(disposing);}publicArraySegment<byte>GetBuffer()=>new(_buffer,0,_length);publicbyte[]ToArray(){vararr=newbyte[_length];Buffer.BlockCopy(_buffer,0,arr,0,_length);returnarr;}privatevoidEnsureCapacity(intvalue){if((uint)value>(uint)_maxBufferSize)// value cast handles overflow to negative as well{throwCreateOverCapacityException(_maxBufferSize);}elseif(value>_buffer.Length){Grow(value);}}privatevoidGrow(intvalue){Debug.Assert(value>_buffer.Length);// Extract the current buffer to be replaced.varcurrentBuffer=_buffer;_buffer=null!;// Determine the capacity to request for the new buffer. It should be// at least twice as long as the current one, if not more if the requested// value is more than that. If the new value would put it longer than the max// allowed byte array, than shrink to that (and if the required length is actually// longer than that, we'll let the runtime throw).vartwiceLength=2*(uint)currentBuffer.Length;varnewCapacity=twiceLength>int.MaxValue?Math.Max(value,int.MaxValue):Math.Max(value,(int)twiceLength);// Get a new buffer, copy the current one to it, return the current one, and// set the new buffer as current.varnewBuffer=ArrayPool<byte>.Shared.Rent(newCapacity);Buffer.BlockCopy(currentBuffer,0,newBuffer,0,_length);ArrayPool<byte>.Shared.Return(currentBuffer);_buffer=newBuffer;}publicoverridevoidWrite(byte[]buffer,intoffset,intcount){Debug.Assert(buffer!=null);Debug.Assert(offset>=0);Debug.Assert(count>=0);EnsureCapacity(_length+count);Buffer.BlockCopy(buffer,offset,_buffer,_length,count);_length+=count;}publicoverrideTaskWriteAsync(byte[]buffer,intoffset,intcount,CancellationTokencancellationToken){Write(buffer,offset,count);returnTask.CompletedTask;}publicoverridevoidWriteByte(bytevalue){varnewLength=_length+1;EnsureCapacity(newLength);_buffer[_length]=value;_length=newLength;}publicoverridevoidFlush(){}publicoverrideTaskFlushAsync(CancellationTokencancellationToken)=>Task.CompletedTask;publicoverridelongLength=>_length;publicoverrideboolCanWrite=>true;publicoverrideboolCanRead=>false;publicoverrideboolCanSeek=>false;publicoverridelongPosition{get{thrownewNotSupportedException();}set{thrownewNotSupportedException();}}publicoverrideintRead(byte[]buffer,intoffset,intcount){thrownewNotSupportedException();}publicoverridelongSeek(longoffset,SeekOriginorigin){thrownewNotSupportedException();}publicoverridevoidSetLength(longvalue){thrownewNotSupportedException();}privatestaticInvalidOperationExceptionCreateOverCapacityException(intmaxBufferSize){returnnewInvalidOperationException($"Buffer size exceeded maximum of {maxBufferSize} bytes.");}}
And we use it in the following way (deserializing soap content):
usingvarrequestMessage=newHttpRequestMessage(HttpMethod.Post,uri){Content=httpContent};usingvarmessage=awaitclient.SendAsync(requestMessage,HttpCompletionOption.ResponseHeadersRead,ct).ConfigureAwait(false);usingvardata=newLimitArrayPoolWriteStream(int.MaxValue,message.Content.Headers.ContentLength??256);awaitmessage.Content.CopyToAsync(data).ConfigureAwait(false);varbufferSegment=data.GetBuffer();// Convert the byte buffer to a string which we can then deserialize into XML (or whatever)varresultContent=Encoding.UTF8.GetString(bufferSegment.Array,bufferSegment.Offset,bufferSegment.Count);
In our case, the servers don't support sending back the Content-Length header - but that might be different in this case (and more efficient) when supported.
The text was updated successfully, but these errors were encountered:
We tried this, and we will keep it in mind for later, but for now there is hardly any benefit in this. Our deserializers only take string input, and consume an order of magnitude more memory than the overhead of the HTTP client. In other words: the potential gain from this is very limited. On top of this, we are forced to cast everything to strings anyways as to not break our API.
Once again, this may become beneficial in the future, but would require a major overhaul to be noticeable.
I was recently doing some improvement on my own HttpClient usage in libraries of our design after reading through https://xaml.dev/post/removing-memory-allocations-in-http-requests-using-arraypools, and someone on our team asked if we could improve allocation when utilizing this library. We are using this library to communicate with various FHIR endpoints, but we're finding there's a lot of allocation that seems that it could be optimized.
It does look possible when looking at HttpClientRequester
So, I've created a netstandard2.0 compatible version of
LimitArrayPoolWriteStream
that we use for this purpose (see article, we adapted this for use in netstandard2 which might be applicable here).And we use it in the following way (deserializing soap content):
In our case, the servers don't support sending back the Content-Length header - but that might be different in this case (and more efficient) when supported.
The text was updated successfully, but these errors were encountered: