网络发包的优化
网络发送数据包,现成的接口Socket.Send
在我的电脑上大概会有700us左右的耗时,如果使用SendAsync
开销会更大,并且会导致数据包的乱序。
Send阻塞接口慢的原因一方面底层会跑很多代码,另一方面有线程就有竞争,有竞争就有加锁。
于是写了一个轻量的Queue来加速网络发包,平均耗时从70us降低到300ns。
public static void Main1()
{
// create the socket
Socket listenSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
// bind the listening socket to the port
IPAddress hostIP = IPAddress.Any;
IPEndPoint endpoint = new IPEndPoint(hostIP, 10042);
listenSocket.Bind(endpoint);
// start listening
listenSocket.Listen(2);
var socket = listenSocket.Accept();
ConfigureTcpSocket(socket);
int count = 0;
Stopwatch sw = new Stopwatch();
for (int i = 0; i < 100; i++)
{
count++;
sw.Restart();
sw.Stop();
Console.WriteLine($"{count} stopwatch empty body, time {sw.Elapsed.Ticks}");
}
SuperQueue<byte[]> superQueue = new SuperQueue<byte[]>(100);
Task.Factory.StartNew(() =>
{
while (true)
{
if (superQueue.Count == 0)
{
Thread.Sleep(0);
continue;
}
superQueue.TryDequeue(out byte[] data);
var byteSent = socket.Send(data);
Console.WriteLine($"{count} socket send {byteSent}");
Console.Out.Flush();
}
});
for (int i = 0; i < 100; i++)
{
count++;
sw.Restart();
superQueue.TryEnqueue(new byte[1024]);
sw.Stop();
Console.WriteLine($"{count} stopwatch superQueue, time {sw.Elapsed.Ticks}");
}
count = 0;
for (int i = 0; i < 100; i++)
{
count++;
sw.Restart();
int nSent = socket.Send(new byte[1024]);
sw.Stop();
Console.WriteLine($"{count} socket send {nSent} bytes, time {sw.Elapsed.Ticks}");
}
count = 0;
for (int i = 0; i < 100; i++)
{
int newCount = count++;
sw.Restart();
socket.SendAsync(new ArraySegment<byte>(new byte[1024]), SocketFlags.None).ContinueWith(task =>
{
Console.WriteLine($"{newCount} socket send {task.Result}");
});
sw.Stop();
Console.WriteLine($"ASYNC: {newCount} socket send, time {sw.Elapsed.Ticks}");
}
Console.ReadLine();
}
public class SuperQueue<T>
where T : class
{
private T[] array;
private int head; // The index from which to dequeue if the queue isn't empty.
//tail只跟生产者有关
private int tail; // The index at which to enqueue if the queue isn't full.
private int size; // Number of elements.
public int Count => size;
public SuperQueue(int capacity)
{
if (capacity < 0)
throw new ArgumentOutOfRangeException(nameof(capacity), capacity, "ArgumentOutOfRange_NeedNonNegNum");
array = new T[capacity];
}
//生产者线程
public bool TryEnqueue(T item)
{
if (size == array.Length)
{
return false;
}
array[tail] = item;
MoveNext(ref tail);
Interlocked.Increment(ref size);
return true;
}
public bool TryDequeue(out T result)
{
if (size == 0)
{
result = default;
return false;
}
result = array[head];
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
{
array[head] = default;
}
MoveNext(ref head);
Interlocked.Decrement(ref size);
return true;
}
// Increments the index wrapping it if necessary.
private void MoveNext(ref int index)
{
// It is tempting to use the remainder operator here but it is actually much slower
// than a simple comparison and a rarely taken branch.
// JIT produces better code than with ternary operator ?:
int tmp = index + 1;
if (tmp == array.Length)
{
tmp = 0;
}
index = tmp;
}
}