Synchronously write HTTP responses#713
Conversation
|
@MDA2AV would love to have your review on this. I know that |
Hmm interesting, so the idea is to go fully sync on the response which is nice, I see that you're using u8 literals for less allocations, that is the best performant solution for all the const data. What is really missing is to use PipeWriter, while Stream has an overload to accept ReadOnlySpan<>: My guess would be that this aggressive array pooling will hurt performance, which wouldn't happen with PipeWriter.Write(). My sugestion for performance test here would be to create a "short-circuit" response like a full u8 header and call Stream.Write() only once, and then compare to the current implementation, also testing with pipelined requests can amplify the results difference. Output.Write(DateHeader.GetValue()); -> This can be improved to avoid calculating the date every call probably As we discussed also before, some objects like ResponseHandler could be pooled but that can be a difficult adjustment that could require supporting parameterless constructors. You can cache the entire Status line indexed by status code number, quite fast but would be a small gain. Edit: Also try not to change a lot of things at once, it can hide perf loss/wins when benchmarking, would be interesting to see if the current changes have any performance changes, which environment are you using to benchmark? Edit: ref structs are difficult to implement and aren't really going to improve much if you're fully using spans already. If you need a non GC'able memory you can try using pinned arrays, they are basically as fast as stack memory |
|
For the DateHelper check this impl ](https://github.com/MDA2AV/Wired.IO/blob/main/Wired.IO/Utilities/DateHelper.cs) I adapted it from the asp net platform benchmark, cached values updated every 1 second |
|
Thanks, I have some caching for the date header, but it is a good idea to cache the whole header line at once. The Regarding the Stream implementation: I agree that I need to investigate public override void Write(ReadOnlySpan<byte> buffer)
{
var count = buffer.Length;
if (count < (Buffer.Length - Current))
{
buffer.CopyTo(Buffer.AsSpan(Current));
Current += count;
if (Current == Buffer.Length)
{
WriteBuffer();
}
}
else
{
WriteBuffer();
Stream.Write(buffer);
}
}This buffer holds 65 kb by default, so this will cause most responses to be written synchronously to the buffer and then flushed once to the underlying stream. |
- Add a specific overload for the newly used span based write methods - Use specific stream type to avoid virtual calls
I'll try a benchmark with the date, wonder how much overhead the lock adds. I need to look deeper into this PoolBufferedStream, I'm still inclined to the PipeWriter because it is perfect for sync writing and you can easily swap between stream and pipe, these are just "views" to the memory, I've always thought as use Stream for async and heap allocated buffer manipulation and PipeWriter for sync, but let's not be fooled here, the memory is in the heap for both cases and it is GC managed.. If we want an unmanaged slab of memory in the heap we would need to deal with something like: I'd love to see actual numbers though, my guess would be that using Stream with sync writes or PipeWriter is likely to yield similar performance but sometimes we can be surprised |
|
Yes, this is basically what pipe writer does when writing to a stream. Instead of array pool they use a memory pool which also uses pinned memory. So there surely will be performance benefits. I will create an issue to switch to a pipe writer (either directly connected to the socket for HTTP or to the sslstream for HTTPS). With sockets this would be as efficient as it can be (in .NET SDK terms), with SSL it manages a pinned buffer which is written to the underlying stream when flushed. |
No description provided.