Skip to content

Commit 4fa39de

Browse files
andrews-unityShadauxCatmattwalsh-unity
authored
perf: Reuse static array when sending data to transport to avoid FastBufferWriter.ToArray() GC (#1324) (#1345)
* perf: Reuse static array when sending data to transport to avoid FastBufferWriter.ToArray() GC * Added failsafe if the value is too large. Co-authored-by: Jaedyn Draper <284434+ShadauxCat@users.noreply.github.com> Co-authored-by: Matt Walsh <69258106+mattwalsh-unity@users.noreply.github.com>
1 parent 06d9744 commit 4fa39de

File tree

2 files changed

+30
-6
lines changed

2 files changed

+30
-6
lines changed

com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,14 +149,9 @@ public NetworkManagerMessageSender(NetworkManager manager)
149149

150150
public void Send(ulong clientId, NetworkDelivery delivery, FastBufferWriter batchData)
151151
{
152-
153-
var length = batchData.Length;
154-
//TODO: Transport needs to have a way to send it data without copying and allocating here.
155-
var bytes = batchData.ToArray();
156-
var sendBuffer = new ArraySegment<byte>(bytes, 0, length);
152+
var sendBuffer = batchData.ToTempByteArray();
157153

158154
m_NetworkManager.NetworkConfig.NetworkTransport.Send(clientId, sendBuffer, delivery);
159-
160155
}
161156
}
162157

com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ internal struct WriterHandle
2424

2525
internal readonly unsafe WriterHandle* Handle;
2626

27+
private static byte[] s_ByteArrayCache = new byte[65535];
28+
2729
/// <summary>
2830
/// The current write position
2931
/// </summary>
@@ -78,6 +80,10 @@ internal unsafe void CommitBitwiseWrites(int amount)
7880
/// <param name="maxSize">Maximum size the buffer can grow to. If less than size, buffer cannot grow.</param>
7981
public unsafe FastBufferWriter(int size, Allocator allocator, int maxSize = -1)
8082
{
83+
// Allocating both the Handle struct and the buffer in a single allocation - sizeof(WriterHandle) + size
84+
// The buffer for the initial allocation is the next block of memory after the handle itself.
85+
// If the buffer grows, a new buffer will be allocated and the handle pointer pointed at the new location...
86+
// The original buffer won't be deallocated until the writer is destroyed since it's part of the handle allocation.
8187
Handle = (WriterHandle*)UnsafeUtility.Malloc(sizeof(WriterHandle) + size, UnsafeUtility.AlignOf<WriterHandle>(), allocator);
8288
#if DEVELOPMENT_BUILD || UNITY_EDITOR
8389
UnsafeUtility.MemSet(Handle, 0, sizeof(WriterHandle) + size);
@@ -349,6 +355,29 @@ public unsafe byte[] ToArray()
349355
return ret;
350356
}
351357

358+
/// <summary>
359+
/// Uses a static cached array to create an array segment with no allocations.
360+
/// This array can only be used until the next time ToTempByteArray() is called on ANY FastBufferWriter,
361+
/// as the cached buffer is shared by all of them and will be overwritten.
362+
/// As such, this should be used with care.
363+
/// </summary>
364+
/// <returns></returns>
365+
internal unsafe ArraySegment<byte> ToTempByteArray()
366+
{
367+
var length = Length;
368+
if (length > s_ByteArrayCache.Length)
369+
{
370+
return new ArraySegment<byte>(ToArray(), 0, length);
371+
}
372+
373+
fixed (byte* b = s_ByteArrayCache)
374+
{
375+
UnsafeUtility.MemCpy(b, Handle->BufferPointer, length);
376+
}
377+
378+
return new ArraySegment<byte>(s_ByteArrayCache, 0, length);
379+
}
380+
352381
/// <summary>
353382
/// Gets a direct pointer to the underlying buffer
354383
/// </summary>

0 commit comments

Comments
 (0)