diff --git a/LiteDB.Benchmarks/Benchmarks/Constants.cs b/LiteDB.Benchmarks/Benchmarks/Constants.cs index 3de1c953e..ac2a752f0 100644 --- a/LiteDB.Benchmarks/Benchmarks/Constants.cs +++ b/LiteDB.Benchmarks/Benchmarks/Constants.cs @@ -10,6 +10,7 @@ internal static class Categories internal const string QUERIES = nameof(QUERIES); internal const string INSERTION = nameof(INSERTION); internal const string DELETION = nameof(DELETION); + internal const string SERIALIZATION = nameof(SERIALIZATION); } } } \ No newline at end of file diff --git a/LiteDB.Benchmarks/Benchmarks/Serialization/BufferSerializationBenchmark.cs b/LiteDB.Benchmarks/Benchmarks/Serialization/BufferSerializationBenchmark.cs new file mode 100644 index 000000000..e2cc9012e --- /dev/null +++ b/LiteDB.Benchmarks/Benchmarks/Serialization/BufferSerializationBenchmark.cs @@ -0,0 +1,128 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; +using LiteDB.Benchmarks.Benchmarks; +using LiteDB.Engine; +using System; + +namespace LiteDB.Benchmarks.Benchmarks.Serialization +{ + [BenchmarkCategory(Constants.Categories.SERIALIZATION)] + [SimpleJob(RuntimeMoniker.Net80)] + [MemoryDiagnoser] + public class BufferSerializationBenchmark + { + private BufferSlice _contiguousRead; + private BufferSlice[] _splitRead; + private BufferSlice _contiguousWrite; + private BufferSlice[] _splitWrite; + + [Params(128, 4096)] + public int ValueCount { get; set; } + + [GlobalSetup] + public void Setup() + { + var byteCount = ValueCount * sizeof(int); + + var contiguous = new byte[byteCount]; + using (var writer = new BufferWriter(contiguous)) + { + for (var i = 0; i < ValueCount; i++) + { + writer.Write(i); + } + } + + _contiguousRead = new BufferSlice(contiguous, 0, contiguous.Length); + + var firstLength = Math.Max(1, byteCount / 2); + var secondLength = byteCount - firstLength; + + if (secondLength == 0) + { + secondLength = 1; + firstLength = Math.Max(1, byteCount - secondLength); + } + + var segmentA = new BufferSlice(new byte[firstLength], 0, firstLength); + var segmentB = new BufferSlice(new byte[secondLength], 0, secondLength); + + using (var writer = new BufferWriter(new[] { segmentA, segmentB })) + { + for (var i = 0; i < ValueCount; i++) + { + writer.Write(i); + } + } + + _splitRead = new[] { segmentA, segmentB }; + + _contiguousWrite = new BufferSlice(new byte[byteCount], 0, byteCount); + _splitWrite = new[] + { + new BufferSlice(new byte[firstLength], 0, firstLength), + new BufferSlice(new byte[secondLength], 0, secondLength) + }; + } + + [Benchmark] + public int ReadInt32Contiguous() + { + var sum = 0; + + using (var reader = new BufferReader(_contiguousRead)) + { + for (var i = 0; i < ValueCount; i++) + { + sum += reader.ReadInt32(); + } + } + + return sum; + } + + [Benchmark] + public int ReadInt32Split() + { + var sum = 0; + + using (var reader = new BufferReader(_splitRead)) + { + for (var i = 0; i < ValueCount; i++) + { + sum += reader.ReadInt32(); + } + } + + return sum; + } + + [Benchmark] + public int WriteInt32Contiguous() + { + using (var writer = new BufferWriter(_contiguousWrite)) + { + for (var i = 0; i < ValueCount; i++) + { + writer.Write(i); + } + + return writer.Position; + } + } + + [Benchmark] + public int WriteInt32Split() + { + using (var writer = new BufferWriter(_splitWrite)) + { + for (var i = 0; i < ValueCount; i++) + { + writer.Write(i); + } + + return writer.Position; + } + } + } +} diff --git a/LiteDB/Engine/Disk/Serializer/BufferReader.cs b/LiteDB/Engine/Disk/Serializer/BufferReader.cs index 980ff2542..23d41d9fa 100644 --- a/LiteDB/Engine/Disk/Serializer/BufferReader.cs +++ b/LiteDB/Engine/Disk/Serializer/BufferReader.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Buffers; +using System.Buffers.Binary; using System.Collections.Generic; using System.IO; using static LiteDB.Constants; @@ -89,45 +90,106 @@ private bool MoveForward(int count) return false; } - /// - /// Read bytes from source and copy into buffer. Return how many bytes was read - /// - public int Read(byte[] buffer, int offset, int count) + private void EnsureCurrentSegment() + { + if (_currentPosition < _current.Count) + { + return; + } + + if (this.MoveForward(0) == false && _isEOF) + { + return; + } + } + + private void Advance(int count) { - var bufferPosition = 0; + var remaining = count; - while (bufferPosition < count) + while (remaining > 0) { + this.EnsureCurrentSegment(); + + if (_isEOF) + { + break; + } + var bytesLeft = _current.Count - _currentPosition; - var bytesToCopy = Math.Min(count - bufferPosition, bytesLeft); + var bytesToSkip = Math.Min(remaining, bytesLeft); - // fill buffer - if (buffer != null) + this.MoveForward(bytesToSkip); + + remaining -= bytesToSkip; + } + + ENSURE(remaining == 0, "current value must fit inside defined buffer"); + } + + private void ReadTo(Span destination) + { + var remaining = destination.Length; + var destOffset = 0; + + while (remaining > 0) + { + this.EnsureCurrentSegment(); + + if (_isEOF) { - Buffer.BlockCopy(_current.Array, - _current.Offset + _currentPosition, - buffer, - offset + bufferPosition, - bytesToCopy); + break; } - bufferPosition += bytesToCopy; + var bytesLeft = _current.Count - _currentPosition; + var bytesToCopy = Math.Min(remaining, bytesLeft); + + _current.AsSpan(_currentPosition, bytesToCopy) + .CopyTo(destination.Slice(destOffset, bytesToCopy)); - // move position in current segment (and go to next segment if finish) this.MoveForward(bytesToCopy); - if (_isEOF) break; + destOffset += bytesToCopy; + remaining -= bytesToCopy; } - ENSURE(count == bufferPosition, "current value must fit inside defined buffer"); + ENSURE(remaining == 0, "current value must fit inside defined buffer"); + } - return bufferPosition; + /// + /// Read bytes from source and copy into buffer. Return how many bytes was read + /// + public int Read(byte[] buffer, int offset, int count) + { + if (count == 0) + { + return 0; + } + + if (buffer == null) + { + this.Advance(count); + return count; + } + + this.ReadTo(buffer.AsSpan(offset, count)); + + return count; } /// /// Skip bytes (same as Read but with no array copy) /// - public int Skip(int count) => this.Read(null, 0, count); + public int Skip(int count) + { + if (count == 0) + { + return 0; + } + + this.Advance(count); + return count; + } /// /// Consume all data source until finish @@ -173,39 +235,137 @@ private bool TryReadCStringCurrentSegment(out string value) #endregion - #region Read Numbers - - private T ReadNumber(Func convert, int size) + #region Read Numbers + + public Int32 ReadInt32() { - T value; + const int size = 4; - // if fits in current segment, use inner array - otherwise copy from multiples segments if (_currentPosition + size <= _current.Count) { - value = convert(_current.Array, _current.Offset + _currentPosition); + var value = BinaryPrimitives.ReadInt32LittleEndian(_current.AsSpan(_currentPosition, size)); this.MoveForward(size); + + return value; } - else + + Span buffer = stackalloc byte[size]; + + this.ReadTo(buffer); + + return BinaryPrimitives.ReadInt32LittleEndian(buffer); + } + + public Int64 ReadInt64() + { + const int size = 8; + + if (_currentPosition + size <= _current.Count) + { + var value = BinaryPrimitives.ReadInt64LittleEndian(_current.AsSpan(_currentPosition, size)); + + this.MoveForward(size); + + return value; + } + + Span buffer = stackalloc byte[size]; + + this.ReadTo(buffer); + + return BinaryPrimitives.ReadInt64LittleEndian(buffer); + } + + public UInt16 ReadUInt16() + { + const int size = 2; + + if (_currentPosition + size <= _current.Count) { - var buffer = _bufferPool.Rent(size); + var value = BinaryPrimitives.ReadUInt16LittleEndian(_current.AsSpan(_currentPosition, size)); + + this.MoveForward(size); - this.Read(buffer, 0, size); + return value; + } - value = convert(buffer, 0); + Span buffer = stackalloc byte[size]; - _bufferPool.Return(buffer, true); + this.ReadTo(buffer); + + return BinaryPrimitives.ReadUInt16LittleEndian(buffer); + } + + public UInt32 ReadUInt32() + { + const int size = 4; + + if (_currentPosition + size <= _current.Count) + { + var value = BinaryPrimitives.ReadUInt32LittleEndian(_current.AsSpan(_currentPosition, size)); + + this.MoveForward(size); + + return value; } - return value; + Span buffer = stackalloc byte[size]; + + this.ReadTo(buffer); + + return BinaryPrimitives.ReadUInt32LittleEndian(buffer); } - public Int32 ReadInt32() => this.ReadNumber(BitConverter.ToInt32, 4); - public Int64 ReadInt64() => this.ReadNumber(BitConverter.ToInt64, 8); - public UInt16 ReadUInt16() => this.ReadNumber(BitConverter.ToUInt16, 2); - public UInt32 ReadUInt32() => this.ReadNumber(BitConverter.ToUInt32, 4); - public Single ReadSingle() => this.ReadNumber(BitConverter.ToSingle, 4); - public Double ReadDouble() => this.ReadNumber(BitConverter.ToDouble, 8); + public Single ReadSingle() + { + const int size = 4; + + if (_currentPosition + size <= _current.Count) + { + var bits = BinaryPrimitives.ReadInt32LittleEndian(_current.AsSpan(_currentPosition, size)); + + this.MoveForward(size); + + return Int32BitsToSingle(bits); + } + + Span buffer = stackalloc byte[size]; + + this.ReadTo(buffer); + + var value = BinaryPrimitives.ReadInt32LittleEndian(buffer); + + return Int32BitsToSingle(value); + } + + public Double ReadDouble() + { + const int size = 8; + + if (_currentPosition + size <= _current.Count) + { + var bits = BinaryPrimitives.ReadInt64LittleEndian(_current.AsSpan(_currentPosition, size)); + + this.MoveForward(size); + + return BitConverter.Int64BitsToDouble(bits); + } + + Span buffer = stackalloc byte[size]; + + this.ReadTo(buffer); + + var value = BinaryPrimitives.ReadInt64LittleEndian(buffer); + + return BitConverter.Int64BitsToDouble(value); + } + + private static unsafe float Int32BitsToSingle(int value) + { + // Unsafe re-interpretation keeps Engine serializer span-based without extra allocations. + return *(float*)&value; + } public Decimal ReadDecimal() { @@ -562,4 +722,5 @@ public void Dispose() _source?.Dispose(); } } -} \ No newline at end of file +} + diff --git a/LiteDB/Engine/Disk/Serializer/BufferWriter.cs b/LiteDB/Engine/Disk/Serializer/BufferWriter.cs index 83c6c26f8..6129404d4 100644 --- a/LiteDB/Engine/Disk/Serializer/BufferWriter.cs +++ b/LiteDB/Engine/Disk/Serializer/BufferWriter.cs @@ -1,6 +1,8 @@ -using System; +using System; using System.Buffers; +using System.Buffers.Binary; using System.Collections.Generic; +using System.Runtime.InteropServices; using static LiteDB.Constants; namespace LiteDB.Engine @@ -85,39 +87,91 @@ private bool MoveForward(int count) return false; } - /// - /// Write bytes from buffer into segmentsr. Return how many bytes was write - /// - public int Write(byte[] buffer, int offset, int count) + private void EnsureCurrentSegment() + { + if (_currentPosition < _current.Count) + { + return; + } + + if (this.MoveForward(0) == false && _isEOF) + { + return; + } + } + + private void Advance(int count) { - var bufferPosition = 0; + var remaining = count; - while (bufferPosition < count) + while (remaining > 0) { + this.EnsureCurrentSegment(); + + if (_isEOF) + { + break; + } + var bytesLeft = _current.Count - _currentPosition; - var bytesToCopy = Math.Min(count - bufferPosition, bytesLeft); + var bytesToSkip = Math.Min(remaining, bytesLeft); + + this.MoveForward(bytesToSkip); - // fill buffer - if (buffer != null) + remaining -= bytesToSkip; + } + + ENSURE(remaining == 0, "current value must fit inside defined buffer"); + } + + private void Write(ReadOnlySpan source) + { + var remaining = source.Length; + var sourceOffset = 0; + + while (remaining > 0) + { + this.EnsureCurrentSegment(); + + if (_isEOF) { - Buffer.BlockCopy(buffer, - offset + bufferPosition, - _current.Array, - _current.Offset + _currentPosition, - bytesToCopy); + break; } - bufferPosition += bytesToCopy; + var bytesLeft = _current.Count - _currentPosition; + var bytesToCopy = Math.Min(remaining, bytesLeft); + + source.Slice(sourceOffset, bytesToCopy) + .CopyTo(_current.AsWritableSpan(_currentPosition, bytesToCopy)); - // move position in current segment (and go to next segment if finish) this.MoveForward(bytesToCopy); - if (_isEOF) break; + sourceOffset += bytesToCopy; + remaining -= bytesToCopy; + } + + ENSURE(remaining == 0, "current value must fit inside defined buffer"); + } + + /// + /// Write bytes from buffer into segmentsr. Return how many bytes was write + /// + public int Write(byte[] buffer, int offset, int count) + { + if (count == 0) + { + return 0; } - ENSURE(count == bufferPosition, "current value must fit inside defined buffer"); + if (buffer == null) + { + this.Advance(count); + return count; + } - return bufferPosition; + this.Write(buffer.AsSpan(offset, count)); + + return count; } /// @@ -128,7 +182,16 @@ public int Write(byte[] buffer, int offset, int count) /// /// Skip bytes (same as Write but with no array copy) /// - public int Skip(int count) => this.Write(null, 0, count); + public int Skip(int count) + { + if (count == 0) + { + return 0; + } + + this.Advance(count); + return count; + } /// /// Consume all data source until finish @@ -160,34 +223,110 @@ public void Consume() #endregion - #region Numbers + #region Numbers - private void WriteNumber(T value, Action toBytes, int size) + public void Write(Int32 value) { + const int size = 4; + if (_currentPosition + size <= _current.Count) { - toBytes(value, _current.Array, _current.Offset + _currentPosition); + BinaryPrimitives.WriteInt32LittleEndian(_current.AsWritableSpan(_currentPosition, size), value); this.MoveForward(size); + return; } - else + + Span buffer = stackalloc byte[size]; + BinaryPrimitives.WriteInt32LittleEndian(buffer, value); + this.Write(buffer); + } + + public void Write(Int64 value) + { + const int size = 8; + + if (_currentPosition + size <= _current.Count) { - var buffer = _bufferPool.Rent(size); + BinaryPrimitives.WriteInt64LittleEndian(_current.AsWritableSpan(_currentPosition, size), value); - toBytes(value, buffer, 0); + this.MoveForward(size); + return; + } - this.Write(buffer, 0, size); + Span buffer = stackalloc byte[size]; + BinaryPrimitives.WriteInt64LittleEndian(buffer, value); + this.Write(buffer); + } - _bufferPool.Return(buffer, true); + public void Write(UInt16 value) + { + const int size = 2; + + if (_currentPosition + size <= _current.Count) + { + BinaryPrimitives.WriteUInt16LittleEndian(_current.AsWritableSpan(_currentPosition, size), value); + + this.MoveForward(size); + return; } + + Span buffer = stackalloc byte[size]; + BinaryPrimitives.WriteUInt16LittleEndian(buffer, value); + this.Write(buffer); } - public void Write(Int32 value) => this.WriteNumber(value, BufferExtensions.ToBytes, 4); - public void Write(Int64 value) => this.WriteNumber(value, BufferExtensions.ToBytes, 8); - public void Write(UInt16 value) => this.WriteNumber(value, BufferExtensions.ToBytes, 2); - public void Write(UInt32 value) => this.WriteNumber(value, BufferExtensions.ToBytes, 4); - public void Write(Single value) => this.WriteNumber(value, BufferExtensions.ToBytes, 4); - public void Write(Double value) => this.WriteNumber(value, BufferExtensions.ToBytes, 8); + public void Write(UInt32 value) + { + const int size = 4; + + if (_currentPosition + size <= _current.Count) + { + BinaryPrimitives.WriteUInt32LittleEndian(_current.AsWritableSpan(_currentPosition, size), value); + + this.MoveForward(size); + return; + } + + Span buffer = stackalloc byte[size]; + BinaryPrimitives.WriteUInt32LittleEndian(buffer, value); + this.Write(buffer); + } + + public void Write(Single value) + { + const int size = 4; + + if (_currentPosition + size <= _current.Count) + { + MemoryMarshal.Write(_current.AsWritableSpan(_currentPosition, size), ref value); + + this.MoveForward(size); + return; + } + + Span buffer = stackalloc byte[size]; + MemoryMarshal.Write(buffer, ref value); + this.Write(buffer); + } + + public void Write(Double value) + { + const int size = 8; + var bits = BitConverter.DoubleToInt64Bits(value); + + if (_currentPosition + size <= _current.Count) + { + BinaryPrimitives.WriteInt64LittleEndian(_current.AsWritableSpan(_currentPosition, size), bits); + + this.MoveForward(size); + return; + } + + Span buffer = stackalloc byte[size]; + BinaryPrimitives.WriteInt64LittleEndian(buffer, bits); + this.Write(buffer); + } public void Write(Decimal value) { @@ -217,10 +356,22 @@ public void Write(DateTime value) /// public void Write(Guid value) { - // there is no avaiable value.TryWriteBytes (TODO: implement conditional compile)? - var bytes = value.ToByteArray(); + const int size = 16; + + if (_currentPosition + size <= _current.Count) + { + MemoryMarshal.Write(_current.AsWritableSpan(_currentPosition, size), ref value); - this.Write(bytes, 0, 16); + this.MoveForward(size); + } + else + { + Span buffer = stackalloc byte[size]; + + MemoryMarshal.Write(buffer, ref value); + + this.Write(buffer); + } } /// @@ -444,3 +595,5 @@ public void Dispose() } } } + + diff --git a/LiteDB/LiteDB.csproj b/LiteDB/LiteDB.csproj index c1a6f2070..07e04de7e 100644 --- a/LiteDB/LiteDB.csproj +++ b/LiteDB/LiteDB.csproj @@ -1,4 +1,4 @@ - + netstandard2.0;net8.0 @@ -63,9 +63,10 @@ - + + diff --git a/LiteDB/Utils/BufferSlice.cs b/LiteDB/Utils/BufferSlice.cs index 62fc5071f..7f91d30db 100644 --- a/LiteDB/Utils/BufferSlice.cs +++ b/LiteDB/Utils/BufferSlice.cs @@ -34,6 +34,16 @@ public byte this[int index] set => this.Array[this.Offset + index] = value; } + public ReadOnlySpan AsSpan(int offset, int length) + { + return new ReadOnlySpan(this.Array, this.Offset + offset, length); + } + + public Span AsWritableSpan(int offset, int length) + { + return new Span(this.Array, this.Offset + offset, length); + } + /// /// Clear all page content byte array (not controls) /// diff --git a/LiteDB/Utils/Constants.cs b/LiteDB/Utils/Constants.cs index 8f0d0eeb4..a949a5227 100644 --- a/LiteDB/Utils/Constants.cs +++ b/LiteDB/Utils/Constants.cs @@ -1,4 +1,4 @@ -using LiteDB.Engine; +using LiteDB.Engine; using System; using System.Diagnostics; @@ -6,6 +6,7 @@ using System.Threading; [assembly: InternalsVisibleTo("LiteDB.Tests")] +[assembly: InternalsVisibleTo("LiteDB.Benchmarks")] #if DEBUG || TESTING [assembly: InternalsVisibleTo("ConsoleApp1")] #endif @@ -198,4 +199,4 @@ public static void DEBUG(bool conditional, string message = null) } } } -} \ No newline at end of file +} diff --git a/LiteDB/Utils/Extensions/BufferSliceExtensions.cs b/LiteDB/Utils/Extensions/BufferSliceExtensions.cs index 049ce931f..684d8e2f1 100644 --- a/LiteDB/Utils/Extensions/BufferSliceExtensions.cs +++ b/LiteDB/Utils/Extensions/BufferSliceExtensions.cs @@ -1,6 +1,6 @@ using LiteDB.Engine; using System; -using System.Linq; +using System.Buffers.Binary; using System.Text; using static LiteDB.Constants; @@ -22,22 +22,22 @@ public static byte ReadByte(this BufferSlice buffer, int offset) public static Int16 ReadInt16(this BufferSlice buffer, int offset) { - return BitConverter.ToInt16(buffer.Array, buffer.Offset + offset); + return BinaryPrimitives.ReadInt16LittleEndian(buffer.AsSpan(offset, sizeof(short))); } public static UInt16 ReadUInt16(this BufferSlice buffer, int offset) { - return BitConverter.ToUInt16(buffer.Array, buffer.Offset + offset); + return BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset, sizeof(ushort))); } public static Int32 ReadInt32(this BufferSlice buffer, int offset) { - return BitConverter.ToInt32(buffer.Array, buffer.Offset + offset); + return BinaryPrimitives.ReadInt32LittleEndian(buffer.AsSpan(offset, sizeof(int))); } public static UInt32 ReadUInt32(this BufferSlice buffer, int offset) { - return BitConverter.ToUInt32(buffer.Array, buffer.Offset + offset); + return BinaryPrimitives.ReadUInt32LittleEndian(buffer.AsSpan(offset, sizeof(uint))); } public static float ReadSingle(this BufferSlice buffer, int offset) @@ -47,12 +47,13 @@ public static float ReadSingle(this BufferSlice buffer, int offset) public static Int64 ReadInt64(this BufferSlice buffer, int offset) { - return BitConverter.ToInt64(buffer.Array, buffer.Offset + offset); + return BinaryPrimitives.ReadInt64LittleEndian(buffer.AsSpan(offset, sizeof(long))); } public static double ReadDouble(this BufferSlice buffer, int offset) { - return BitConverter.ToDouble(buffer.Array, buffer.Offset + offset); + var bits = BinaryPrimitives.ReadInt64LittleEndian(buffer.AsSpan(offset, sizeof(long))); + return BitConverter.Int64BitsToDouble(bits); } public static Decimal ReadDecimal(this BufferSlice buffer, int offset) @@ -201,22 +202,22 @@ public static void Write(this BufferSlice buffer, byte value, int offset) public static void Write(this BufferSlice buffer, Int16 value, int offset) { - value.ToBytes(buffer.Array, buffer.Offset + offset); + BinaryPrimitives.WriteInt16LittleEndian(buffer.AsWritableSpan(offset, sizeof(short)), value); } public static void Write(this BufferSlice buffer, UInt16 value, int offset) { - value.ToBytes(buffer.Array, buffer.Offset + offset); + BinaryPrimitives.WriteUInt16LittleEndian(buffer.AsWritableSpan(offset, sizeof(ushort)), value); } public static void Write(this BufferSlice buffer, Int32 value, int offset) { - value.ToBytes(buffer.Array, buffer.Offset + offset); + BinaryPrimitives.WriteInt32LittleEndian(buffer.AsWritableSpan(offset, sizeof(int)), value); } public static void Write(this BufferSlice buffer, UInt32 value, int offset) { - value.ToBytes(buffer.Array, buffer.Offset + offset); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.AsWritableSpan(offset, sizeof(uint)), value); } public static void Write(this BufferSlice buffer, float value, int offset) @@ -226,12 +227,13 @@ public static void Write(this BufferSlice buffer, float value, int offset) public static void Write(this BufferSlice buffer, Int64 value, int offset) { - value.ToBytes(buffer.Array, buffer.Offset + offset); + BinaryPrimitives.WriteInt64LittleEndian(buffer.AsWritableSpan(offset, sizeof(long)), value); } public static void Write(this BufferSlice buffer, Double value, int offset) { - value.ToBytes(buffer.Array, buffer.Offset + offset); + var bits = BitConverter.DoubleToInt64Bits(value); + BinaryPrimitives.WriteInt64LittleEndian(buffer.AsWritableSpan(offset, sizeof(long)), bits); } public static void Write(this BufferSlice buffer, Decimal value, int offset) @@ -245,12 +247,13 @@ public static void Write(this BufferSlice buffer, Decimal value, int offset) public static void Write(this BufferSlice buffer, DateTime value, int offset) { - value.ToUniversalTime().Ticks.ToBytes(buffer.Array, buffer.Offset + offset); + var ticks = value.ToUniversalTime().Ticks; + BinaryPrimitives.WriteInt64LittleEndian(buffer.AsWritableSpan(offset, sizeof(long)), ticks); } public static void Write(this BufferSlice buffer, PageAddress value, int offset) { - value.PageID.ToBytes(buffer.Array, buffer.Offset + offset); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.AsWritableSpan(offset, sizeof(uint)), value.PageID); buffer[offset + 4] = value.Index; } @@ -277,7 +280,7 @@ public static void Write(this BufferSlice buffer, ObjectId value, int offset) public static void Write(this BufferSlice buffer, byte[] value, int offset) { - Buffer.BlockCopy(value, 0, buffer.Array, buffer.Offset + offset, value.Length); + value.AsSpan().CopyTo(buffer.AsWritableSpan(offset, value.Length)); } public static void Write(this BufferSlice buffer, string value, int offset)