| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 |
- using System;
- using System.Buffers;
- using System.Diagnostics;
- using System.IO;
- using System.Text;
- using System.Text.Unicode;
- using BenchmarkDotNet.Attributes;
- using Lidgren.Network;
- using NetSerializer;
- using Robust.Shared.Analyzers;
- namespace Content.Benchmarks
- {
- // Code for the *Slow and *Unsafe implementations taken from NetSerializer, licensed under the MIT license.
- [MemoryDiagnoser]
- [Virtual]
- public class NetSerializerStringBenchmark
- {
- private const int StringByteBufferLength = 256;
- private const int StringCharBufferLength = 128;
- private string _toSerialize;
- [Params(8, 64, 256, 1024)]
- public int StringLength { get; set; }
- private readonly MemoryStream _outputStream = new(2048);
- private readonly MemoryStream _inputStream = new(2048);
- [GlobalSetup]
- public void Setup()
- {
- Span<byte> buf = stackalloc byte[StringLength / 2];
- new Random().NextBytes(buf);
- _toSerialize = NetUtility.ToHexString(buf);
- Primitives.WritePrimitive(_inputStream, _toSerialize);
- }
- [Benchmark]
- public void BenchWriteCore()
- {
- _outputStream.Position = 0;
- WritePrimitiveCore(_outputStream, _toSerialize);
- }
- [Benchmark]
- public void BenchReadCore()
- {
- _inputStream.Position = 0;
- ReadPrimitiveCore(_inputStream, out _);
- }
- [Benchmark]
- public void BenchWriteUnsafe()
- {
- _outputStream.Position = 0;
- WritePrimitiveUnsafe(_outputStream, _toSerialize);
- }
- [Benchmark]
- public void BenchReadUnsafe()
- {
- _inputStream.Position = 0;
- ReadPrimitiveUnsafe(_inputStream, out _);
- }
- [Benchmark]
- public void BenchWriteSlow()
- {
- _outputStream.Position = 0;
- WritePrimitiveSlow(_outputStream, _toSerialize);
- }
- [Benchmark]
- public void BenchReadSlow()
- {
- _inputStream.Position = 0;
- ReadPrimitiveSlow(_inputStream, out _);
- }
- public static void WritePrimitiveCore(Stream stream, string value)
- {
- if (value == null)
- {
- Primitives.WritePrimitive(stream, (uint) 0);
- return;
- }
- if (value.Length == 0)
- {
- Primitives.WritePrimitive(stream, (uint) 1);
- return;
- }
- Span<byte> buf = stackalloc byte[StringByteBufferLength];
- var totalChars = value.Length;
- var totalBytes = Encoding.UTF8.GetByteCount(value);
- Primitives.WritePrimitive(stream, (uint) totalBytes + 1);
- Primitives.WritePrimitive(stream, (uint) totalChars);
- var totalRead = 0;
- ReadOnlySpan<char> span = value;
- while (true)
- {
- var finalChunk = totalRead + totalChars >= totalChars;
- Utf8.FromUtf16(span, buf, out var read, out var wrote, isFinalBlock: finalChunk);
- stream.Write(buf[0..wrote]);
- totalRead += read;
- if (read >= totalChars)
- {
- break;
- }
- span = span[read..];
- totalChars -= read;
- }
- }
- public static void ReadPrimitiveCore(Stream stream, out string value)
- {
- Primitives.ReadPrimitive(stream, out uint totalBytes);
- if (totalBytes == 0)
- {
- value = null;
- return;
- }
- if (totalBytes == 1)
- {
- value = string.Empty;
- return;
- }
- totalBytes -= 1;
- Primitives.ReadPrimitive(stream, out uint totalChars);
- value = string.Create((int) totalChars, ((int) totalBytes, stream), StringSpanRead);
- }
- private static void StringSpanRead(Span<char> span, (int totalBytes, Stream stream) tuple)
- {
- Span<byte> buf = stackalloc byte[StringByteBufferLength];
- // ReSharper disable VariableHidesOuterVariable
- var (totalBytes, stream) = tuple;
- // ReSharper restore VariableHidesOuterVariable
- var totalBytesRead = 0;
- var totalCharsRead = 0;
- var writeBufStart = 0;
- while (totalBytesRead < totalBytes)
- {
- var bytesLeft = totalBytes - totalBytesRead;
- var bytesReadLeft = Math.Min(buf.Length, bytesLeft);
- var writeSlice = buf[writeBufStart..(bytesReadLeft - writeBufStart)];
- var bytesInBuffer = stream.Read(writeSlice);
- if (bytesInBuffer == 0) throw new EndOfStreamException();
- var readFromStream = bytesInBuffer + writeBufStart;
- var final = readFromStream == bytesLeft;
- var status = Utf8.ToUtf16(buf[..readFromStream], span[totalCharsRead..], out var bytesRead, out var charsRead, isFinalBlock: final);
- totalBytesRead += bytesRead;
- totalCharsRead += charsRead;
- writeBufStart = 0;
- if (status == OperationStatus.DestinationTooSmall)
- {
- // Malformed data?
- throw new InvalidDataException();
- }
- if (status == OperationStatus.NeedMoreData)
- {
- // We got cut short in the middle of a multi-byte UTF-8 sequence.
- // So we need to move it to the bottom of the span, then read the next bit *past* that.
- // This copy should be fine because we're only ever gonna be copying up to 4 bytes
- // from the end of the buffer to the start.
- // So no chance of overlap.
- buf[bytesRead..].CopyTo(buf);
- writeBufStart = bytesReadLeft - bytesRead;
- continue;
- }
- Debug.Assert(status == OperationStatus.Done);
- }
- }
- public static void WritePrimitiveSlow(Stream stream, string value)
- {
- if (value == null)
- {
- Primitives.WritePrimitive(stream, (uint) 0);
- return;
- }
- else if (value.Length == 0)
- {
- Primitives.WritePrimitive(stream, (uint) 1);
- return;
- }
- var encoding = new UTF8Encoding(false, true);
- var len = encoding.GetByteCount(value);
- Primitives.WritePrimitive(stream, (uint) len + 1);
- Primitives.WritePrimitive(stream, (uint) value.Length);
- var buf = new byte[len];
- encoding.GetBytes(value, 0, value.Length, buf, 0);
- stream.Write(buf, 0, len);
- }
- public static void ReadPrimitiveSlow(Stream stream, out string value)
- {
- Primitives.ReadPrimitive(stream, out uint len);
- if (len == 0)
- {
- value = null;
- return;
- }
- else if (len == 1)
- {
- value = string.Empty;
- return;
- }
- Primitives.ReadPrimitive(stream, out uint _);
- len -= 1;
- var encoding = new UTF8Encoding(false, true);
- var buf = new byte[len];
- var l = 0;
- while (l < len)
- {
- var r = stream.Read(buf, l, (int) len - l);
- if (r == 0)
- throw new EndOfStreamException();
- l += r;
- }
- value = encoding.GetString(buf);
- }
- private sealed class StringHelper
- {
- public StringHelper()
- {
- Encoding = new UTF8Encoding(false, true);
- }
- private Encoder _encoder;
- private Decoder _decoder;
- private byte[] _byteBuffer;
- private char[] _charBuffer;
- public UTF8Encoding Encoding { get; private set; }
- public Encoder Encoder
- {
- get
- {
- _encoder ??= Encoding.GetEncoder();
- return _encoder;
- }
- }
- public Decoder Decoder
- {
- get
- {
- _decoder ??= Encoding.GetDecoder();
- return _decoder;
- }
- }
- public byte[] ByteBuffer
- {
- get
- {
- _byteBuffer ??= new byte[StringByteBufferLength];
- return _byteBuffer;
- }
- }
- public char[] CharBuffer
- {
- get
- {
- _charBuffer ??= new char[StringCharBufferLength];
- return _charBuffer;
- }
- }
- }
- [ThreadStatic]
- private static StringHelper _stringHelper;
- public static unsafe void WritePrimitiveUnsafe(Stream stream, string value)
- {
- if (value == null)
- {
- Primitives.WritePrimitive(stream, (uint) 0);
- return;
- }
- else if (value.Length == 0)
- {
- Primitives.WritePrimitive(stream, (uint) 1);
- return;
- }
- var helper = _stringHelper;
- if (helper == null)
- _stringHelper = helper = new StringHelper();
- var encoder = helper.Encoder;
- var buf = helper.ByteBuffer;
- var totalChars = value.Length;
- int totalBytes;
- fixed (char* ptr = value)
- totalBytes = encoder.GetByteCount(ptr, totalChars, true);
- Primitives.WritePrimitive(stream, (uint) totalBytes + 1);
- Primitives.WritePrimitive(stream, (uint) totalChars);
- var p = 0;
- var completed = false;
- while (completed == false)
- {
- int charsConverted;
- int bytesConverted;
- fixed (char* src = value)
- fixed (byte* dst = buf)
- {
- encoder.Convert(src + p, totalChars - p, dst, buf.Length, true,
- out charsConverted, out bytesConverted, out completed);
- }
- stream.Write(buf, 0, bytesConverted);
- p += charsConverted;
- }
- }
- public static void ReadPrimitiveUnsafe(Stream stream, out string value)
- {
- Primitives.ReadPrimitive(stream, out uint totalBytes);
- if (totalBytes == 0)
- {
- value = null;
- return;
- }
- else if (totalBytes == 1)
- {
- value = string.Empty;
- return;
- }
- totalBytes -= 1;
- Primitives.ReadPrimitive(stream, out uint totalChars);
- var helper = _stringHelper;
- if (helper == null)
- _stringHelper = helper = new StringHelper();
- var decoder = helper.Decoder;
- var buf = helper.ByteBuffer;
- char[] chars;
- if (totalChars <= StringCharBufferLength)
- chars = helper.CharBuffer;
- else
- chars = new char[totalChars];
- var streamBytesLeft = (int) totalBytes;
- var cp = 0;
- while (streamBytesLeft > 0)
- {
- var bytesInBuffer = stream.Read(buf, 0, Math.Min(buf.Length, streamBytesLeft));
- if (bytesInBuffer == 0)
- throw new EndOfStreamException();
- streamBytesLeft -= bytesInBuffer;
- var flush = streamBytesLeft == 0;
- var completed = false;
- var p = 0;
- while (completed == false)
- {
- decoder.Convert(
- buf,
- p,
- bytesInBuffer - p,
- chars,
- cp,
- (int) totalChars - cp,
- flush,
- out var bytesConverted,
- out var charsConverted,
- out completed
- );
- p += bytesConverted;
- cp += charsConverted;
- }
- }
- value = new string(chars, 0, (int) totalChars);
- }
- }
- }
|