-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Description
(offline customer report)
SerializeHeaders is allocating 1.09% memory, 0.63% for GetBytes, 0.32% for growing List. Allocation is certainly an issue here, but what worries me more is the possible contention for allocating and freeing GC handles.
Notice here the caller is calling GetBytes(string), so a new byte array is allocated every time. It would be more efficient to (re)use a single byte array, converting all strings into it, then pin the array once. The byte array can be reused, then no need for GC handles.
So I need to check caller code to see how it’s used. Here is the code:
List<GCHandle> pinnedHeaders = SerializeHeaders(isOpaqueUpgrade);
try
{
if (dataChunks != null)
{
if (pinnedHeaders == null)
{
pinnedHeaders = new List<GCHandle>();
}
var handle = GCHandle.Alloc(dataChunks, GCHandleType.Pinned);
pinnedHeaders.Add(handle);
_nativeResponse.Response_V1.EntityChunkCount = (ushort)dataChunks.Length;
_nativeResponse.Response_V1.pEntityChunks = (HttpApiTypes.HTTP_DATA_CHUNK*)handle.AddrOfPinnedObject();
}
...
}
finally
{
FreePinnedHeaders(pinnedHeaders);
}
This contains all the lines using pinnedHeaders.
Here is one part within SerialiizeHeaders:
bytes = HeaderEncoding.GetBytes(headerName);
unknownHeaders[_nativeResponse.Response_V1.Headers.UnknownHeaderCount].NameLength = (ushort)bytes.Length;
gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
unknownHeaders[_nativeResponse.Response_V1.Headers.UnknownHeaderCount].pName = (byte*)gcHandle.AddrOfPinnedObject();
Here will be my suggestion:
internal unsafe class PinnedHeaders
{
private List<GCHandle> pinnedHeaders;
private byte[] buffer;
private int bufferPos;
private int bufferLength;
private byte* pinnedBuffer;
internal PinnedHeaders(byte[] buffer, byte * pinnedBuffer)
{
this.buffer = buffer;
this.bufferLength = buffer.Length;
this.pinnedBuffer = pinnedBuffer;
this.pinnedHeaders = new List<GCHandle>();
}
internal byte* AddString(string text, Encoding encoding)
{
int length = encoding.GetByteCount(text);
if ((this.bufferPos + length) < this.bufferLength)
{
encoding.GetBytes(text, 0, text.Length, buffer, this.bufferPos);
var result = this.pinnedBuffer + this.bufferPos;
this.bufferPos += length;
return result;
}
byte[] encoded = encoding.GetBytes(text);
var handle = GCHandle.Alloc(encoded, GCHandleType.Pinned);
this.pinnedHeaders.Add(handle);
return (byte*)handle.AddrOfPinnedObject();
}
}
The byte[] buffer can be reused across calls. This should minimize the need for allocating GC handles for strings/byte[]).