Skip to content

Commit

Permalink
perf: Increasing Network Writer performance (#1674)
Browse files Browse the repository at this point in the history
* Increasing performance of network writer

* Using Array.Clear

renaming value to newLength

* Update Assets/Mirror/Tests/Editor/NetworkWriterTest.cs

Co-Authored-By: Paul Pacheco <paulpach@gmail.com>

* adding braces

* extracting EnsureCapacity method

* Update NetworkWriter.cs

Co-authored-by: Paul Pacheco <paulpach@gmail.com>
Co-authored-by: vis2k <info@noobtuts.com>
  • Loading branch information
3 people committed Apr 10, 2020
1 parent c1fe212 commit f057983
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 55 deletions.
2 changes: 1 addition & 1 deletion Assets/Mirror/Runtime/LocalConnections.cs
Expand Up @@ -71,7 +71,7 @@ public ArraySegment<byte> GetNextPacket()

public void ResetBuffer()
{
writer.SetLength(0);
writer.Reset();
reader.Position = 0;
}
}
Expand Down
122 changes: 76 additions & 46 deletions Assets/Mirror/Runtime/NetworkWriter.cs
@@ -1,4 +1,5 @@
using System;
using System.Runtime.CompilerServices;
using System.Text;
using UnityEngine;

Expand All @@ -19,22 +20,68 @@ public class NetworkWriter

// 'int' is the best type for .Position. 'short' is too small if we send >32kb which would result in negative .Position
// -> converting long to int is fine until 2GB of data (MAX_INT), so we don't have to worry about overflows here
public int Position;

int position;
int length;

public int Length
public int Length => length;

public int Position
{
get => length;
private set
get => position;
set
{
position = value;
EnsureLength(value);
}
}

/// <summary>
/// Reset both the position and length of the stream
/// </summary>
/// <remarks>
/// Leaves the capacity the same so that we can reuse this writer without extra allocations
/// </remarks>
public void Reset()
{
position = 0;
length = 0;
}

/// <summary>
/// Sets length, moves position if it is greater than new length
/// </summary>
/// <param name="newLength"></param>
/// <remarks>
/// Zeros out any extra length created by setlength
/// </remarks>
public void SetLength(int newLength)
{
int oldLength = length;

// ensure length & capacity
EnsureLength(newLength);

// zero out new length
if (oldLength < newLength)
{
Array.Clear(buffer, oldLength, newLength - oldLength);
}

length = newLength;
position = Mathf.Min(position, length);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
void EnsureLength(int value)
{
if (length < value)
{
EnsureCapacity(value);
length = value;
if (Position > length)
Position = length;
EnsureCapacity(value);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
void EnsureCapacity(int value)
{
if (buffer.Length < value)
Expand All @@ -51,8 +98,8 @@ void EnsureCapacity(int value)
// ToArray returns all the data we have written, regardless of the current position
public byte[] ToArray()
{
byte[] data = new byte[Length];
Array.ConstrainedCopy(buffer, 0, data, 0, Length);
byte[] data = new byte[length];
Array.ConstrainedCopy(buffer, 0, data, 0, length);
return data;
}

Expand All @@ -66,61 +113,44 @@ public ArraySegment<byte> ToArraySegment()
return new ArraySegment<byte>(buffer, 0, length);
}

// reset both the position and length of the stream, but leaves the capacity the same
// so that we can reuse this writer without extra allocations
public void SetLength(int value)
{
Length = value;
}

public void WriteByte(byte value)
{
if (Position >= Length)
{
Length += 1;
}

buffer[Position++] = value;
EnsureLength(position + 1);
buffer[position++] = value;
}


// for byte arrays with consistent size, where the reader knows how many to read
// (like a packet opcode that's always the same)
public void WriteBytes(byte[] buffer, int offset, int count)
{
// no null check because we would need to write size info for that too (hence WriteBytesAndSize)
if (Position + count > Length)
{
Length = Position + count;
}
Array.ConstrainedCopy(buffer, offset, this.buffer, Position, count);
Position += count;
EnsureLength(position + count);
Array.ConstrainedCopy(buffer, offset, this.buffer, position, count);
position += count;
}

public void WriteUInt32(uint value)
{
EnsureCapacity(Position + 4);
buffer[Position++] = (byte)value;
buffer[Position++] = (byte)(value >> 8);
buffer[Position++] = (byte)(value >> 16);
buffer[Position++] = (byte)(value >> 24);
Length = Math.Max(Length, Position);
EnsureLength(position + 4);
buffer[position++] = (byte)value;
buffer[position++] = (byte)(value >> 8);
buffer[position++] = (byte)(value >> 16);
buffer[position++] = (byte)(value >> 24);
}

public void WriteInt32(int value) => WriteUInt32((uint)value);

public void WriteUInt64(ulong value)
{
EnsureCapacity(Position + 8);
buffer[Position++] = (byte)value;
buffer[Position++] = (byte)(value >> 8);
buffer[Position++] = (byte)(value >> 16);
buffer[Position++] = (byte)(value >> 24);
buffer[Position++] = (byte)(value >> 32);
buffer[Position++] = (byte)(value >> 40);
buffer[Position++] = (byte)(value >> 48);
buffer[Position++] = (byte)(value >> 56);
Length = Math.Max(Length, Position);
EnsureLength(position + 8);
buffer[position++] = (byte)value;
buffer[position++] = (byte)(value >> 8);
buffer[position++] = (byte)(value >> 16);
buffer[position++] = (byte)(value >> 24);
buffer[position++] = (byte)(value >> 32);
buffer[position++] = (byte)(value >> 40);
buffer[position++] = (byte)(value >> 48);
buffer[position++] = (byte)(value >> 56);
}

public void WriteInt64(long value) => WriteUInt64((ulong)value);
Expand Down
2 changes: 1 addition & 1 deletion Assets/Mirror/Runtime/NetworkWriterPool.cs
Expand Up @@ -72,7 +72,7 @@ public static PooledNetworkWriter GetWriter()
next--;

// reset cached writer length and position
writer.SetLength(0);
writer.Reset();
return writer;
}

Expand Down
30 changes: 23 additions & 7 deletions Assets/Mirror/Tests/Editor/NetworkWriterTest.cs
Expand Up @@ -109,26 +109,42 @@ public void TestSetLengthZeroes()
writer.WriteInt64(0xA_FADED_DEAD_EEL);
writer.WriteString("and ate it");
int position = writer.Position;

writer.SetLength(10);
// Setting length should set position too
Assert.That(writer.Position, Is.EqualTo(10));
Assert.That(writer.Position, Is.EqualTo(10), "Decreasing length should move position");

// lets grow it back and check there's zeroes now.
writer.SetLength(position);
byte[] data = writer.ToArray();
for (int i = position; i < data.Length; i++)
for (int i = 10; i < data.Length; i++)
{
Assert.That(data[i], Is.EqualTo(0), $"index {i} should have value 0");
}
}

[Test]
public void TestSetLengthInitialization()
{
NetworkWriter writer = new NetworkWriter();

writer.SetLength(10);
// Setting length should leave position at 0
Assert.That(writer.Position, Is.EqualTo(0), "Increasing length should not move position");
}

[Test]
public void TestResetSetsPotionAndLength()
{
NetworkWriter writer = new NetworkWriter();
writer.WriteString("I saw");
writer.WriteInt64(0xA_FADED_DEAD_EEL);
writer.WriteString("and ate it");
writer.Reset();

Assert.That(writer.Position, Is.EqualTo(0));
Assert.That(writer.Length, Is.EqualTo(0));

byte[] data = writer.ToArray();
for (int i = 0; i < data.Length; i++)
Assert.That(data[i], Is.EqualTo(0), $"index {i} should have value 0");
Assert.That(data, Is.Empty);
}

[Test]
Expand Down Expand Up @@ -504,7 +520,7 @@ public void TestReadingTruncatedString()
{
NetworkWriter writer = new NetworkWriter();
writer.WriteString("a string longer than 10 bytes");
writer.SetLength(10);
writer.Reset();
NetworkReader reader = new NetworkReader(writer.ToArray());
Assert.Throws<System.IO.EndOfStreamException>(() => reader.ReadString());
}
Expand Down

0 comments on commit f057983

Please sign in to comment.