-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Double the size of the previous segment #66930
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -178,7 +178,7 @@ private void AllocateWriteHeadSynchronized(int sizeHint) | |
if (_writingHead == null) | ||
{ | ||
// We need to allocate memory to write since nobody has written before | ||
BufferSegment newSegment = AllocateSegment(sizeHint); | ||
BufferSegment newSegment = AllocateSegment(MinimumSegmentSize, sizeHint); | ||
|
||
// Set all the pointers | ||
_writingHead = _readHead = _readTail = newSegment; | ||
|
@@ -197,7 +197,9 @@ private void AllocateWriteHeadSynchronized(int sizeHint) | |
_writingHeadBytesBuffered = 0; | ||
} | ||
|
||
BufferSegment newSegment = AllocateSegment(sizeHint); | ||
// Double the minimum segment size on subsequent segements | ||
int newSegmentSize = Math.Min(PipeOptions.MaximumSegmentSize, _writingHead.Capacity * 2); | ||
BufferSegment newSegment = AllocateSegment(newSegmentSize, sizeHint); | ||
|
||
_writingHead.SetNext(newSegment); | ||
_writingHead = newSegment; | ||
|
@@ -206,7 +208,7 @@ private void AllocateWriteHeadSynchronized(int sizeHint) | |
} | ||
} | ||
|
||
private BufferSegment AllocateSegment(int sizeHint) | ||
private BufferSegment AllocateSegment(int minimumSegmentSize, int sizeHint) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: We should rename the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes we should but I dislike unrelated changes. I'll make that change as well. |
||
{ | ||
Debug.Assert(sizeHint >= 0); | ||
BufferSegment newSegment = CreateSegmentUnsynchronized(); | ||
|
@@ -223,12 +225,12 @@ private BufferSegment AllocateSegment(int sizeHint) | |
if (sizeHint <= maxSize) | ||
{ | ||
// Use the specified pool as it fits. Specified pool is not null as maxSize == -1 if _pool is null. | ||
newSegment.SetOwnedMemory(pool!.Rent(GetSegmentSize(sizeHint, maxSize))); | ||
newSegment.SetOwnedMemory(pool!.Rent(GetSegmentSize(minimumSegmentSize, sizeHint, maxSize))); | ||
} | ||
else | ||
{ | ||
// Use the array pool | ||
int sizeToRequest = GetSegmentSize(sizeHint); | ||
int sizeToRequest = GetSegmentSize(minimumSegmentSize, sizeHint); | ||
newSegment.SetOwnedMemory(ArrayPool<byte>.Shared.Rent(sizeToRequest)); | ||
} | ||
|
||
|
@@ -237,10 +239,11 @@ private BufferSegment AllocateSegment(int sizeHint) | |
return newSegment; | ||
} | ||
|
||
private int GetSegmentSize(int sizeHint, int maxBufferSize = int.MaxValue) | ||
private static int GetSegmentSize(int minimumSegmentSize, int sizeHint, int maxBufferSize = int.MaxValue) | ||
{ | ||
// First we need to handle case where hint is smaller than minimum segment size | ||
sizeHint = Math.Max(MinimumSegmentSize, sizeHint); | ||
sizeHint = Math.Max(minimumSegmentSize, sizeHint); | ||
|
||
// After that adjust it to fit into pools max buffer size | ||
int adjustedToMaximumSize = Math.Min(maxBufferSize, sizeHint); | ||
return adjustedToMaximumSize; | ||
|
@@ -1090,7 +1093,8 @@ private void WriteMultiSegment(ReadOnlySpan<byte> source) | |
|
||
// This is optimized to use pooled memory. That's why we pass 0 instead of | ||
// source.Length | ||
BufferSegment newSegment = AllocateSegment(0); | ||
int newSegmentSize = Math.Min(PipeOptions.MaximumSegmentSize, _writingHead.Capacity * 2); | ||
BufferSegment newSegment = AllocateSegment(newSegmentSize, 0); | ||
|
||
_writingHead.SetNext(newSegment); | ||
_writingHead = newSegment; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,9 @@ public class PipeOptions | |
{ | ||
private const int DefaultMinimumSegmentSize = 4096; | ||
|
||
// Arbitrary 1MB max segment size | ||
internal const int MaximumSegmentSize = 1024 * 1024; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this should be higher for large copy scenarios? Not sure. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should think about how this interacts with logic like that for SocketConnection.MinAllocBufferSize. I could see us potentially unnecessarily doing syscalls using relatively small 2KB buffer for reads after this change as we reach the end of the block. This is a DoS mitigation given smaller segment sizes. But with larger segment sizes, we could leave more bytes unfilled before allocating syscalls without wasting too much memory proportionally. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, we'll need to tweak how we think about this (and if it matters). You'll end up throwing away 2K (which maybe is too aggressive anyways?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think throwing away up to 2K at the end of the segment is that bad especially if the segments get larger. I think we could throw away more. We want to reduce syscalls, so reading into small tail space would be counterproductive given large amounts of data. When we're copying data from one buffer to another in user mode, it makes far more sense to use all the available space. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you suggesting anything or just saying this is interesting? |
||
|
||
/// <summary>Gets the default instance of <see cref="System.IO.Pipelines.PipeOptions" />.</summary> | ||
/// <value>A <see cref="System.IO.Pipelines.PipeOptions" /> object initialized with default parameters.</value> | ||
public static PipeOptions Default { get; } = new PipeOptions(); | ||
|
@@ -38,10 +41,10 @@ public PipeOptions( | |
// to let users specify the maximum buffer size, so we pick a reasonable number based on defaults. They can influence | ||
// how much gets buffered by increasing the minimum segment size. | ||
|
||
// With a defaukt segment size of 4K this maps to 16K | ||
// With a default minimum segment size of 4 this maps to 60K | ||
InitialSegmentPoolSize = 4; | ||
|
||
// With a defaukt segment size of 4K this maps to 1MB. If the pipe has large segments this will be bigger than 1MB... | ||
// With a default minimum segment size of 4K this maps to ~250MB. | ||
MaxSegmentPoolSize = 256; | ||
|
||
// By default, we'll throttle the writer at 64K of buffered data | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -115,7 +115,7 @@ private void AllocateMemory(int sizeHint) | |
if (_head == null) | ||
{ | ||
// We need to allocate memory to write since nobody has written before | ||
BufferSegment newSegment = AllocateSegment(sizeHint); | ||
BufferSegment newSegment = AllocateSegment(_minimumBufferSize, sizeHint); | ||
|
||
// Set all the pointers | ||
_head = _tail = newSegment; | ||
|
@@ -135,15 +135,16 @@ private void AllocateMemory(int sizeHint) | |
_tailBytesBuffered = 0; | ||
} | ||
|
||
BufferSegment newSegment = AllocateSegment(sizeHint); | ||
int newSegmentSize = Math.Min(PipeOptions.MaximumSegmentSize, _tail.Capacity * 2); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is simple to understand but another possible technique could be to base the growth on number of segments rather than the last segment (so taking the speed of the consumer into account to shrink the next segment). I'm not sure if it's a big concern though because:
|
||
BufferSegment newSegment = AllocateSegment(newSegmentSize, sizeHint); | ||
|
||
_tail.SetNext(newSegment); | ||
_tail = newSegment; | ||
} | ||
} | ||
} | ||
|
||
private BufferSegment AllocateSegment(int sizeHint) | ||
private BufferSegment AllocateSegment(int minimumBufferSize, int sizeHint) | ||
{ | ||
Debug.Assert(sizeHint >= 0); | ||
BufferSegment newSegment = CreateSegmentUnsynchronized(); | ||
|
@@ -152,12 +153,12 @@ private BufferSegment AllocateSegment(int sizeHint) | |
if (sizeHint <= maxSize) | ||
{ | ||
// Use the specified pool as it fits. Specified pool is not null as maxSize == -1 if _pool is null. | ||
newSegment.SetOwnedMemory(_pool!.Rent(GetSegmentSize(sizeHint, maxSize))); | ||
newSegment.SetOwnedMemory(_pool!.Rent(GetSegmentSize(minimumBufferSize, sizeHint, maxSize))); | ||
} | ||
else | ||
{ | ||
// Use the array pool | ||
int sizeToRequest = GetSegmentSize(sizeHint); | ||
int sizeToRequest = GetSegmentSize(minimumBufferSize, sizeHint); | ||
newSegment.SetOwnedMemory(ArrayPool<byte>.Shared.Rent(sizeToRequest)); | ||
} | ||
|
||
|
@@ -166,12 +167,12 @@ private BufferSegment AllocateSegment(int sizeHint) | |
return newSegment; | ||
} | ||
|
||
private int GetSegmentSize(int sizeHint, int maxBufferSize = int.MaxValue) | ||
private static int GetSegmentSize(int minimumBufferSize, int sizeHint, int maxBufferSize = int.MaxValue) | ||
{ | ||
// First we need to handle case where hint is smaller than minimum segment size | ||
sizeHint = Math.Max(_minimumBufferSize, sizeHint); | ||
sizeHint = Math.Max(minimumBufferSize, sizeHint); | ||
// After that adjust it to fit into pools max buffer size | ||
var adjustedToMaximumSize = Math.Min(maxBufferSize, sizeHint); | ||
int adjustedToMaximumSize = Math.Min(maxBufferSize, sizeHint); | ||
return adjustedToMaximumSize; | ||
} | ||
|
||
|
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 pool are you testing this with?
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.
ArrayPool. I need to change the implementation of the PinnedBlockMemoryPool to make this work the way it's intended.