зеркало из https://github.com/mono/corefx.git
Sockets on Windows: reduce array allocations during Select, Poll, Receive, Send (#30485)
* untested; zero allocs during Socket.Poll and Socket.Select on Windows (aveat: Socket.Select with > cutoff will still allocate) * use MemoryMarshal.GetReference - cleanly passes null ref for empty span * remove all the "unsafe"; pretty sure that the "ref" will count as a "fixed" during the P/Invoke * avoid allocating in Socket.Send/Socket.Receive when passing multiple segments (synchronous API) * Revert "avoid allocating in Socket.Send/Socket.Receive when passing multiple segments (synchronous API)" This reverts commit 343b88602bd7974f64ae8247f5415b6bf590a89b. * use spans for multi-segment sync send/receive * remove "unsafe" from select - no longer required * fix nit whitespace * address @stephentoub feedback from review: - prefer Foo* to ref Foo in p/invoke - avoid repeated .Count access - use const size stackalloc instead of dynamic - use ArrayPool instead of allocate - avoid multiple testing of count when determining stack vs heap - use smaller stack threshold * add debug assertions to express intent of file descriptor size vs socket list size * use slice+span.clear to simplify objectsToPin wipe
This commit is contained in:
Родитель
30148bf4a5
Коммит
146fe58389
|
@ -40,15 +40,15 @@ internal static partial class Interop
|
|||
|
||||
internal static unsafe SocketError WSARecv(
|
||||
IntPtr socketHandle,
|
||||
WSABuffer[] buffers,
|
||||
Span<WSABuffer> buffers,
|
||||
int bufferCount,
|
||||
out int bytesTransferred,
|
||||
ref SocketFlags socketFlags,
|
||||
NativeOverlapped* overlapped,
|
||||
IntPtr completionRoutine)
|
||||
{
|
||||
Debug.Assert(buffers != null && buffers.Length > 0 );
|
||||
fixed (WSABuffer* buffersPtr = &buffers[0])
|
||||
Debug.Assert(!buffers.IsEmpty);
|
||||
fixed (WSABuffer* buffersPtr = &MemoryMarshal.GetReference(buffers))
|
||||
{
|
||||
return WSARecv(socketHandle, buffersPtr, bufferCount, out bytesTransferred, ref socketFlags, overlapped, completionRoutine);
|
||||
}
|
||||
|
|
|
@ -40,15 +40,15 @@ internal static partial class Interop
|
|||
|
||||
internal static unsafe SocketError WSASend(
|
||||
IntPtr socketHandle,
|
||||
WSABuffer[] buffers,
|
||||
Span<WSABuffer> buffers,
|
||||
int bufferCount,
|
||||
out int bytesTransferred,
|
||||
SocketFlags socketFlags,
|
||||
NativeOverlapped* overlapped,
|
||||
IntPtr completionRoutine)
|
||||
{
|
||||
Debug.Assert(buffers != null && buffers.Length > 0);
|
||||
fixed (WSABuffer* buffersPtr = &buffers[0])
|
||||
Debug.Assert(!buffers.IsEmpty);
|
||||
fixed (WSABuffer* buffersPtr = &MemoryMarshal.GetReference(buffers))
|
||||
{
|
||||
return WSASend(socketHandle, buffersPtr, bufferCount, out bytesTransferred, socketFlags, overlapped, completionRoutine);
|
||||
}
|
||||
|
|
|
@ -10,19 +10,19 @@ internal static partial class Interop
|
|||
internal static partial class Winsock
|
||||
{
|
||||
[DllImport(Interop.Libraries.Ws2_32, SetLastError = true)]
|
||||
internal static extern int select(
|
||||
internal static extern unsafe int select(
|
||||
[In] int ignoredParameter,
|
||||
[In, Out] IntPtr[] readfds,
|
||||
[In, Out] IntPtr[] writefds,
|
||||
[In, Out] IntPtr[] exceptfds,
|
||||
[In] IntPtr* readfds,
|
||||
[In] IntPtr* writefds,
|
||||
[In] IntPtr* exceptfds,
|
||||
[In] ref TimeValue timeout);
|
||||
|
||||
[DllImport(Interop.Libraries.Ws2_32, SetLastError = true)]
|
||||
internal static extern int select(
|
||||
internal static extern unsafe int select(
|
||||
[In] int ignoredParameter,
|
||||
[In, Out] IntPtr[] readfds,
|
||||
[In, Out] IntPtr[] writefds,
|
||||
[In, Out] IntPtr[] exceptfds,
|
||||
[In] IntPtr* readfds,
|
||||
[In] IntPtr* writefds,
|
||||
[In] IntPtr* exceptfds,
|
||||
[In] IntPtr nullTimeout);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
@ -122,16 +123,18 @@ namespace System.Net.Sockets
|
|||
return transmitPackets(socketHandle, packetArray, elementCount, sendSize, overlapped, flags);
|
||||
}
|
||||
|
||||
internal static IntPtr[] SocketListToFileDescriptorSet(IList socketList)
|
||||
internal static void SocketListToFileDescriptorSet(IList socketList, Span<IntPtr> fileDescriptorSet)
|
||||
{
|
||||
if (socketList == null || socketList.Count == 0)
|
||||
int count;
|
||||
if (socketList == null || (count = socketList.Count) == 0)
|
||||
{
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
IntPtr[] fileDescriptorSet = new IntPtr[socketList.Count + 1];
|
||||
fileDescriptorSet[0] = (IntPtr)socketList.Count;
|
||||
for (int current = 0; current < socketList.Count; current++)
|
||||
Debug.Assert(fileDescriptorSet.Length >= count + 1);
|
||||
|
||||
fileDescriptorSet[0] = (IntPtr)count;
|
||||
for (int current = 0; current < count; current++)
|
||||
{
|
||||
if (!(socketList[current] is Socket))
|
||||
{
|
||||
|
@ -140,24 +143,27 @@ namespace System.Net.Sockets
|
|||
|
||||
fileDescriptorSet[current + 1] = ((Socket)socketList[current])._handle.DangerousGetHandle();
|
||||
}
|
||||
return fileDescriptorSet;
|
||||
}
|
||||
|
||||
// Transform the list socketList such that the only sockets left are those
|
||||
// with a file descriptor contained in the array "fileDescriptorArray".
|
||||
internal static void SelectFileDescriptor(IList socketList, IntPtr[] fileDescriptorSet)
|
||||
internal static void SelectFileDescriptor(IList socketList, Span<IntPtr> fileDescriptorSet)
|
||||
{
|
||||
// Walk the list in order.
|
||||
//
|
||||
// Note that the counter is not necessarily incremented at each step;
|
||||
// when the socket is removed, advancing occurs automatically as the
|
||||
// other elements are shifted down.
|
||||
if (socketList == null || socketList.Count == 0)
|
||||
int count;
|
||||
if (socketList == null || (count = socketList.Count) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((int)fileDescriptorSet[0] == 0)
|
||||
Debug.Assert(fileDescriptorSet.Length >= count + 1);
|
||||
|
||||
int returnedCount = (int)fileDescriptorSet[0];
|
||||
if (returnedCount == 0)
|
||||
{
|
||||
// No socket present, will never find any socket, remove them all.
|
||||
socketList.Clear();
|
||||
|
@ -166,13 +172,13 @@ namespace System.Net.Sockets
|
|||
|
||||
lock (socketList)
|
||||
{
|
||||
for (int currentSocket = 0; currentSocket < socketList.Count; currentSocket++)
|
||||
for (int currentSocket = 0; currentSocket < count; currentSocket++)
|
||||
{
|
||||
Socket socket = socketList[currentSocket] as Socket;
|
||||
|
||||
// Look for the file descriptor in the array.
|
||||
int currentFileDescriptor;
|
||||
for (currentFileDescriptor = 0; currentFileDescriptor < (int)fileDescriptorSet[0]; currentFileDescriptor++)
|
||||
for (currentFileDescriptor = 0; currentFileDescriptor < returnedCount; currentFileDescriptor++)
|
||||
{
|
||||
if (fileDescriptorSet[currentFileDescriptor + 1] == socket._handle.DangerousGetHandle())
|
||||
{
|
||||
|
@ -180,10 +186,11 @@ namespace System.Net.Sockets
|
|||
}
|
||||
}
|
||||
|
||||
if (currentFileDescriptor == (int)fileDescriptorSet[0])
|
||||
if (currentFileDescriptor == returnedCount)
|
||||
{
|
||||
// Descriptor not found: remove the current socket and start again.
|
||||
socketList.RemoveAt(currentSocket--);
|
||||
count--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
@ -116,16 +117,31 @@ namespace System.Net.Sockets
|
|||
IntPtr.Zero);
|
||||
return errorCode == SocketError.SocketError ? GetLastSocketError() : SocketError.Success;
|
||||
}
|
||||
|
||||
public static SocketError Send(SafeCloseSocket handle, IList<ArraySegment<byte>> buffers, SocketFlags socketFlags, out int bytesTransferred)
|
||||
{
|
||||
const int StackThreshold = 16; // arbitrary limit to avoid too much space on stack (note: may be over-sized, that's OK - length passed separately)
|
||||
int count = buffers.Count;
|
||||
WSABuffer[] WSABuffers = new WSABuffer[count];
|
||||
GCHandle[] objectsToPin = null;
|
||||
bool useStack = count <= StackThreshold;
|
||||
|
||||
WSABuffer[] leasedWSA = null;
|
||||
GCHandle[] leasedGC = null;
|
||||
Span<WSABuffer> WSABuffers = stackalloc WSABuffer[0];
|
||||
Span<GCHandle> objectsToPin = stackalloc GCHandle[0];
|
||||
if (useStack)
|
||||
{
|
||||
WSABuffers = stackalloc WSABuffer[StackThreshold];
|
||||
objectsToPin = stackalloc GCHandle[StackThreshold];
|
||||
}
|
||||
else
|
||||
{
|
||||
WSABuffers = leasedWSA = ArrayPool<WSABuffer>.Shared.Rent(count);
|
||||
objectsToPin = leasedGC = ArrayPool<GCHandle>.Shared.Rent(count);
|
||||
}
|
||||
objectsToPin = objectsToPin.Slice(0, count);
|
||||
objectsToPin.Clear(); // note: touched in finally
|
||||
|
||||
try
|
||||
{
|
||||
objectsToPin = new GCHandle[count];
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
ArraySegment<byte> buffer = buffers[i];
|
||||
|
@ -156,16 +172,18 @@ namespace System.Net.Sockets
|
|||
}
|
||||
finally
|
||||
{
|
||||
if (objectsToPin != null)
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
for (int i = 0; i < objectsToPin.Length; ++i)
|
||||
if (objectsToPin[i].IsAllocated)
|
||||
{
|
||||
if (objectsToPin[i].IsAllocated)
|
||||
{
|
||||
objectsToPin[i].Free();
|
||||
}
|
||||
objectsToPin[i].Free();
|
||||
}
|
||||
}
|
||||
if (!useStack)
|
||||
{
|
||||
ArrayPool<WSABuffer>.Shared.Return(leasedWSA);
|
||||
ArrayPool<GCHandle>.Shared.Return(leasedGC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,13 +257,29 @@ namespace System.Net.Sockets
|
|||
|
||||
public static SocketError Receive(SafeCloseSocket handle, IList<ArraySegment<byte>> buffers, ref SocketFlags socketFlags, out int bytesTransferred)
|
||||
{
|
||||
const int StackThreshold = 16; // arbitrary limit to avoid too much space on stack (note: may be over-sized, that's OK - length passed separately)
|
||||
int count = buffers.Count;
|
||||
WSABuffer[] WSABuffers = new WSABuffer[count];
|
||||
GCHandle[] objectsToPin = null;
|
||||
bool useStack = count <= StackThreshold;
|
||||
|
||||
WSABuffer[] leasedWSA = null;
|
||||
GCHandle[] leasedGC = null;
|
||||
Span<WSABuffer> WSABuffers = stackalloc WSABuffer[0];
|
||||
Span<GCHandle> objectsToPin = stackalloc GCHandle[0];
|
||||
if (useStack)
|
||||
{
|
||||
WSABuffers = stackalloc WSABuffer[StackThreshold];
|
||||
objectsToPin = stackalloc GCHandle[StackThreshold];
|
||||
}
|
||||
else
|
||||
{
|
||||
WSABuffers = leasedWSA = ArrayPool<WSABuffer>.Shared.Rent(count);
|
||||
objectsToPin = leasedGC = ArrayPool<GCHandle>.Shared.Rent(count);
|
||||
}
|
||||
objectsToPin = objectsToPin.Slice(0, count);
|
||||
objectsToPin.Clear(); // note: touched in finally
|
||||
|
||||
try
|
||||
{
|
||||
objectsToPin = new GCHandle[count];
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
ArraySegment<byte> buffer = buffers[i];
|
||||
|
@ -276,16 +310,18 @@ namespace System.Net.Sockets
|
|||
}
|
||||
finally
|
||||
{
|
||||
if (objectsToPin != null)
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
for (int i = 0; i < objectsToPin.Length; ++i)
|
||||
if (objectsToPin[i].IsAllocated)
|
||||
{
|
||||
if (objectsToPin[i].IsAllocated)
|
||||
{
|
||||
objectsToPin[i].Free();
|
||||
}
|
||||
objectsToPin[i].Free();
|
||||
}
|
||||
}
|
||||
if (!useStack)
|
||||
{
|
||||
ArrayPool<WSABuffer>.Shared.Return(leasedWSA);
|
||||
ArrayPool<GCHandle>.Shared.Return(leasedGC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -668,10 +704,10 @@ namespace System.Net.Sockets
|
|||
return SocketError.Success;
|
||||
}
|
||||
|
||||
public static SocketError Poll(SafeCloseSocket handle, int microseconds, SelectMode mode, out bool status)
|
||||
public static unsafe SocketError Poll(SafeCloseSocket handle, int microseconds, SelectMode mode, out bool status)
|
||||
{
|
||||
IntPtr rawHandle = handle.DangerousGetHandle();
|
||||
IntPtr[] fileDescriptorSet = new IntPtr[2] { (IntPtr)1, rawHandle };
|
||||
IntPtr* fileDescriptorSet = stackalloc IntPtr[2] { (IntPtr)1, rawHandle };
|
||||
Interop.Winsock.TimeValue IOwait = new Interop.Winsock.TimeValue();
|
||||
|
||||
// A negative timeout value implies an indefinite wait.
|
||||
|
@ -708,61 +744,97 @@ namespace System.Net.Sockets
|
|||
return SocketError.Success;
|
||||
}
|
||||
|
||||
public static SocketError Select(IList checkRead, IList checkWrite, IList checkError, int microseconds)
|
||||
public static unsafe SocketError Select(IList checkRead, IList checkWrite, IList checkError, int microseconds)
|
||||
{
|
||||
IntPtr[] readfileDescriptorSet = Socket.SocketListToFileDescriptorSet(checkRead);
|
||||
IntPtr[] writefileDescriptorSet = Socket.SocketListToFileDescriptorSet(checkWrite);
|
||||
IntPtr[] errfileDescriptorSet = Socket.SocketListToFileDescriptorSet(checkError);
|
||||
|
||||
// This code used to erroneously pass a non-null timeval structure containing zeroes
|
||||
// to select() when the caller specified (-1) for the microseconds parameter. That
|
||||
// caused select to actually have a *zero* timeout instead of an infinite timeout
|
||||
// turning the operation into a non-blocking poll.
|
||||
//
|
||||
// Now we pass a null timeval struct when microseconds is (-1).
|
||||
//
|
||||
// Negative microsecond values that weren't exactly (-1) were originally successfully
|
||||
// converted to a timeval struct containing unsigned non-zero integers. This code
|
||||
// retains that behavior so that any app working around the original bug with,
|
||||
// for example, (-2) specified for microseconds, will continue to get the same behavior.
|
||||
|
||||
int socketCount;
|
||||
if (microseconds != -1)
|
||||
const int StackThreshold = 64; // arbitrary limit to avoid too much space on stack
|
||||
bool ShouldStackAlloc(IList list, ref IntPtr[] lease, out Span<IntPtr> span)
|
||||
{
|
||||
Interop.Winsock.TimeValue IOwait = new Interop.Winsock.TimeValue();
|
||||
MicrosecondsToTimeValue((long)(uint)microseconds, ref IOwait);
|
||||
|
||||
socketCount =
|
||||
Interop.Winsock.select(
|
||||
0, // ignored value
|
||||
readfileDescriptorSet,
|
||||
writefileDescriptorSet,
|
||||
errfileDescriptorSet,
|
||||
ref IOwait);
|
||||
}
|
||||
else
|
||||
{
|
||||
socketCount =
|
||||
Interop.Winsock.select(
|
||||
0, // ignored value
|
||||
readfileDescriptorSet,
|
||||
writefileDescriptorSet,
|
||||
errfileDescriptorSet,
|
||||
IntPtr.Zero);
|
||||
int count;
|
||||
if (list == null || (count = list.Count) == 0)
|
||||
{
|
||||
span = default;
|
||||
return false;
|
||||
}
|
||||
if (count >= StackThreshold) // note on >= : the first element is reserved for internal length
|
||||
{
|
||||
span = lease = ArrayPool<IntPtr>.Shared.Rent(count + 1);
|
||||
return false;
|
||||
}
|
||||
span = default;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (NetEventSource.IsEnabled) NetEventSource.Info(null, $"Interop.Winsock.select returns socketCount:{socketCount}");
|
||||
|
||||
if ((SocketError)socketCount == SocketError.SocketError)
|
||||
IntPtr[] leaseRead = null, leaseWrite = null, leaseError = null;
|
||||
try
|
||||
{
|
||||
return GetLastSocketError();
|
||||
Span<IntPtr> readfileDescriptorSet = ShouldStackAlloc(checkRead, ref leaseRead, out var tmp) ? stackalloc IntPtr[StackThreshold] : tmp;
|
||||
Socket.SocketListToFileDescriptorSet(checkRead, readfileDescriptorSet);
|
||||
Span<IntPtr> writefileDescriptorSet = ShouldStackAlloc(checkWrite, ref leaseWrite, out tmp) ? stackalloc IntPtr[StackThreshold] : tmp;
|
||||
Socket.SocketListToFileDescriptorSet(checkWrite, writefileDescriptorSet);
|
||||
Span<IntPtr> errfileDescriptorSet = ShouldStackAlloc(checkError, ref leaseError, out tmp) ? stackalloc IntPtr[StackThreshold] : tmp;
|
||||
Socket.SocketListToFileDescriptorSet(checkError, errfileDescriptorSet);
|
||||
|
||||
// This code used to erroneously pass a non-null timeval structure containing zeroes
|
||||
// to select() when the caller specified (-1) for the microseconds parameter. That
|
||||
// caused select to actually have a *zero* timeout instead of an infinite timeout
|
||||
// turning the operation into a non-blocking poll.
|
||||
//
|
||||
// Now we pass a null timeval struct when microseconds is (-1).
|
||||
//
|
||||
// Negative microsecond values that weren't exactly (-1) were originally successfully
|
||||
// converted to a timeval struct containing unsigned non-zero integers. This code
|
||||
// retains that behavior so that any app working around the original bug with,
|
||||
// for example, (-2) specified for microseconds, will continue to get the same behavior.
|
||||
|
||||
int socketCount;
|
||||
fixed (IntPtr* readPtr = &MemoryMarshal.GetReference(readfileDescriptorSet))
|
||||
fixed (IntPtr* writePtr = &MemoryMarshal.GetReference(writefileDescriptorSet))
|
||||
fixed (IntPtr* errPtr = &MemoryMarshal.GetReference(errfileDescriptorSet))
|
||||
{
|
||||
if (microseconds != -1)
|
||||
{
|
||||
Interop.Winsock.TimeValue IOwait = new Interop.Winsock.TimeValue();
|
||||
MicrosecondsToTimeValue((long)(uint)microseconds, ref IOwait);
|
||||
|
||||
socketCount =
|
||||
Interop.Winsock.select(
|
||||
0, // ignored value
|
||||
readPtr,
|
||||
writePtr,
|
||||
errPtr,
|
||||
ref IOwait);
|
||||
}
|
||||
else
|
||||
{
|
||||
socketCount =
|
||||
Interop.Winsock.select(
|
||||
0, // ignored value
|
||||
readPtr,
|
||||
writePtr,
|
||||
errPtr,
|
||||
IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
if (NetEventSource.IsEnabled)
|
||||
NetEventSource.Info(null, $"Interop.Winsock.select returns socketCount:{socketCount}");
|
||||
|
||||
if ((SocketError)socketCount == SocketError.SocketError)
|
||||
{
|
||||
return GetLastSocketError();
|
||||
}
|
||||
|
||||
Socket.SelectFileDescriptor(checkRead, readfileDescriptorSet);
|
||||
Socket.SelectFileDescriptor(checkWrite, writefileDescriptorSet);
|
||||
Socket.SelectFileDescriptor(checkError, errfileDescriptorSet);
|
||||
|
||||
return SocketError.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (leaseRead != null) ArrayPool<IntPtr>.Shared.Return(leaseRead);
|
||||
if (leaseWrite != null) ArrayPool<IntPtr>.Shared.Return(leaseWrite);
|
||||
if (leaseError != null) ArrayPool<IntPtr>.Shared.Return(leaseError);
|
||||
}
|
||||
|
||||
Socket.SelectFileDescriptor(checkRead, readfileDescriptorSet);
|
||||
Socket.SelectFileDescriptor(checkWrite, writefileDescriptorSet);
|
||||
Socket.SelectFileDescriptor(checkError, errfileDescriptorSet);
|
||||
|
||||
return SocketError.Success;
|
||||
}
|
||||
|
||||
public static SocketError Shutdown(SafeCloseSocket handle, bool isConnected, bool isDisconnected, SocketShutdown how)
|
||||
|
|
Загрузка…
Ссылка в новой задаче