diff --git a/LiteDB/Engine/Disk/DiskService.cs b/LiteDB/Engine/Disk/DiskService.cs index 052ae18e6..567689afd 100644 --- a/LiteDB/Engine/Disk/DiskService.cs +++ b/LiteDB/Engine/Disk/DiskService.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.IO; using System.Threading; @@ -25,6 +26,8 @@ internal class DiskService : IDisposable private long _dataLength; private long _logLength; + private static readonly ArrayPool _bufferPool = ArrayPool.Shared; + public DiskService( EngineSettings settings, EngineState state, @@ -222,11 +225,12 @@ internal void MarkAsInvalidState() { using (var stream = _dataFactory.GetStream(true, true)) { - var buffer = new byte[PAGE_SIZE]; + var buffer = _bufferPool.Rent(PAGE_SIZE); stream.Read(buffer, 0, PAGE_SIZE); buffer[HeaderPage.P_INVALID_DATAFILE_STATE] = 1; stream.Position = 0; stream.Write(buffer, 0, PAGE_SIZE); + _bufferPool.Return(buffer, true); } }); } diff --git a/LiteDB/Engine/Disk/Serializer/BufferPool.cs b/LiteDB/Engine/Disk/Serializer/BufferPool.cs deleted file mode 100644 index 060b72807..000000000 --- a/LiteDB/Engine/Disk/Serializer/BufferPool.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using static LiteDB.Constants; - -namespace LiteDB.Engine -{ - /// - /// Implement similar as ArrayPool for byte array - /// - internal class BufferPool - { - private static readonly object _lock; - private static readonly ArrayPool _bytePool; - - static BufferPool() - { - _lock = new object(); - _bytePool = new ArrayPool(); - } - - public static byte[] Rent(int count) - { - lock (_lock) - { - return _bytePool.Rent(count); - } - } - - public static void Return(byte[] buffer) - { - lock (_lock) - { - _bytePool.Return(buffer); - } - } - } -} \ No newline at end of file diff --git a/LiteDB/Engine/Disk/Serializer/BufferReader.cs b/LiteDB/Engine/Disk/Serializer/BufferReader.cs index c1c07631e..98618124f 100644 --- a/LiteDB/Engine/Disk/Serializer/BufferReader.cs +++ b/LiteDB/Engine/Disk/Serializer/BufferReader.cs @@ -1,7 +1,7 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.IO; -using System.Text; using static LiteDB.Constants; namespace LiteDB.Engine @@ -20,6 +20,8 @@ internal class BufferReader : IDisposable private bool _isEOF = false; + private static readonly ArrayPool _bufferPool = ArrayPool.Shared; + /// /// Current global cursor position /// @@ -102,10 +104,10 @@ public int Read(byte[] buffer, int offset, int count) // fill buffer if (buffer != null) { - Buffer.BlockCopy(_current.Array, - _current.Offset + _currentPosition, - buffer, - offset + bufferPosition, + Buffer.BlockCopy(_current.Array, + _current.Offset + _currentPosition, + buffer, + offset + bufferPosition, bytesToCopy); } @@ -161,13 +163,13 @@ public string ReadString(int count) else { // rent a buffer to be re-usable - var buffer = BufferPool.Rent(count); + var buffer = _bufferPool.Rent(count); this.Read(buffer, 0, count); value = StringEncoding.UTF8.GetString(buffer, 0, count); - BufferPool.Return(buffer); + _bufferPool.Return(buffer, true); } return value; @@ -251,13 +253,13 @@ private T ReadNumber(Func convert, int size) } else { - var buffer = BufferPool.Rent(size); + var buffer = _bufferPool.Rent(size); this.Read(buffer, 0, size); value = convert(buffer, 0); - BufferPool.Return(buffer); + _bufferPool.Return(buffer, true); } return value; @@ -328,13 +330,13 @@ public ObjectId ReadObjectId() } else { - var buffer = BufferPool.Rent(12); + var buffer = _bufferPool.Rent(12); this.Read(buffer, 0, 12); value = new ObjectId(buffer, 0); - BufferPool.Return(buffer); + _bufferPool.Return(buffer, true); } return value; @@ -393,7 +395,7 @@ public BsonValue ReadIndexKey() case BsonType.Int64: return this.ReadInt64(); case BsonType.Double: return this.ReadDouble(); case BsonType.Decimal: return this.ReadDecimal(); - + // Use +1 byte only for length case BsonType.String: return this.ReadString(this.ReadByte()); diff --git a/LiteDB/Engine/Disk/Serializer/BufferWriter.cs b/LiteDB/Engine/Disk/Serializer/BufferWriter.cs index caa43bf3e..11a0fb71e 100644 --- a/LiteDB/Engine/Disk/Serializer/BufferWriter.cs +++ b/LiteDB/Engine/Disk/Serializer/BufferWriter.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.Text; using static LiteDB.Constants; @@ -18,6 +19,8 @@ internal class BufferWriter : IDisposable private bool _isEOF = false; + private static readonly ArrayPool _bufferPool = ArrayPool.Shared; + /// /// Current global cursor position /// @@ -98,10 +101,10 @@ public int Write(byte[] buffer, int offset, int count) // fill buffer if (buffer != null) { - Buffer.BlockCopy(buffer, + Buffer.BlockCopy(buffer, offset + bufferPosition, _current.Array, - _current.Offset + _currentPosition, + _current.Offset + _currentPosition, bytesToCopy); } @@ -133,7 +136,7 @@ public int Write(byte[] buffer, int offset, int count) /// public void Consume() { - if(_source != null) + if (_source != null) { while (_source.MoveNext()) { @@ -166,7 +169,7 @@ public void WriteCString(string value) } else { - var buffer = BufferPool.Rent(bytesCount); + var buffer = _bufferPool.Rent(bytesCount); StringEncoding.UTF8.GetBytes(value, 0, value.Length, buffer, 0); @@ -176,7 +179,7 @@ public void WriteCString(string value) this.MoveForward(1); - BufferPool.Return(buffer); + _bufferPool.Return(buffer, true); } } @@ -202,13 +205,13 @@ public void WriteString(string value, bool specs) else { // rent a buffer to be re-usable - var buffer = BufferPool.Rent(count); + var buffer = _bufferPool.Rent(count); StringEncoding.UTF8.GetBytes(value, 0, value.Length, buffer, 0); this.Write(buffer, 0, count); - BufferPool.Return(buffer); + _bufferPool.Return(buffer, true); } if (specs) @@ -231,13 +234,13 @@ private void WriteNumber(T value, Action toBytes, int size) } else { - var buffer = BufferPool.Rent(size); + var buffer = _bufferPool.Rent(size); toBytes(value, buffer, 0); this.Write(buffer, 0, size); - BufferPool.Return(buffer); + _bufferPool.Return(buffer, true); } } @@ -293,13 +296,13 @@ public void Write(ObjectId value) } else { - var buffer = BufferPool.Rent(12); + var buffer = _bufferPool.Rent(12); value.ToByteArray(buffer, 0); this.Write(buffer, 0, 12); - BufferPool.Return(buffer); + _bufferPool.Return(buffer, true); } } diff --git a/LiteDB/Engine/Disk/Streams/AesStream.cs b/LiteDB/Engine/Disk/Streams/AesStream.cs index fce5cca3b..fe12526bb 100644 --- a/LiteDB/Engine/Disk/Streams/AesStream.cs +++ b/LiteDB/Engine/Disk/Streams/AesStream.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.IO; using System.Linq; using System.Security.Cryptography; @@ -24,6 +25,8 @@ public class AesStream : Stream private static readonly byte[] _emptyContent = new byte[PAGE_SIZE - 1 - 16]; // 1 for aes indicator + 16 for salt + private static readonly ArrayPool _bufferPool = ArrayPool.Shared; + public byte[] Salt { get; } public override bool CanRead => _stream.CanRead; @@ -52,6 +55,11 @@ public AesStream(string password, Stream stream) // start stream from zero position _stream.Position = 0; + const int checkBufferSize = 32; + + var checkBuffer = _bufferPool.Rent(checkBufferSize); + var msBuffer = _bufferPool.Rent(16); + try { // new file? create new salt @@ -104,25 +112,24 @@ public AesStream(string password, Stream stream) // set stream to password checking _stream.Position = 32; - var checkBuffer = new byte[32]; if (!isNew) { // check whether bytes 32 to 64 is empty. This indicates LiteDb was unable to write encrypted 1s during last attempt. - _stream.Read(checkBuffer, 0, checkBuffer.Length); + _stream.Read(checkBuffer, 0, checkBufferSize); isNew = checkBuffer.All(x => x == 0); // reset checkBuffer and stream position - Array.Clear(checkBuffer, 0, checkBuffer.Length); + Array.Clear(checkBuffer, 0, checkBufferSize); _stream.Position = 32; } // fill checkBuffer with encrypted 1 to check when open if (isNew) { - checkBuffer.Fill(1, 0, checkBuffer.Length); + checkBuffer.Fill(1, 0, checkBufferSize); - _writer.Write(checkBuffer, 0, checkBuffer.Length); + _writer.Write(checkBuffer, 0, checkBufferSize); //ensure that the "hidden" page in encrypted files is created correctly _stream.Position = PAGE_SIZE - 1; @@ -130,7 +137,7 @@ public AesStream(string password, Stream stream) } else { - _reader.Read(checkBuffer, 0, checkBuffer.Length); + _reader.Read(checkBuffer, 0, checkBufferSize); if (!checkBuffer.All(x => x == 1)) { @@ -140,8 +147,7 @@ public AesStream(string password, Stream stream) _stream.Position = PAGE_SIZE; _stream.FlushToDisk(); - - using (var ms = new MemoryStream(new byte[16])) + using (var ms = new MemoryStream(msBuffer)) using (var tempStream = new CryptoStream(ms, _decryptor, CryptoStreamMode.Read)) { tempStream.Read(_decryptedZeroes, 0, _decryptedZeroes.Length); @@ -153,6 +159,11 @@ public AesStream(string password, Stream stream) throw; } + finally + { + _bufferPool.Return(msBuffer, true); + _bufferPool.Return(checkBuffer, true); + } } /// diff --git a/LiteDB/Engine/Engine/Upgrade.cs b/LiteDB/Engine/Engine/Upgrade.cs index df6a44896..30beffa6d 100644 --- a/LiteDB/Engine/Engine/Upgrade.cs +++ b/LiteDB/Engine/Engine/Upgrade.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; @@ -9,6 +10,9 @@ namespace LiteDB.Engine { public partial class LiteEngine { + + private static readonly ArrayPool _bufferPool = ArrayPool.Shared; + /// /// If Upgrade=true, run this before open Disk service /// @@ -19,20 +23,23 @@ private void TryUpgrade() // if file not exists, just exit if (!File.Exists(filename)) return; + const int bufferSize = 1024; + var buffer = _bufferPool.Rent(bufferSize); + using (var stream = new FileStream( _settings.Filename, FileMode.Open, FileAccess.Read, - FileShare.Read, 1024)) + FileShare.Read, bufferSize)) { - var buffer = new byte[1024]; + stream.Position = 0; - stream.Read(buffer, 0, buffer.Length); + stream.Read(buffer, 0, bufferSize); if (FileReaderV7.IsVersion(buffer) == false) return; } - + _bufferPool.Return(buffer, true); // run rebuild process this.Recovery(_settings.Collation); } diff --git a/LiteDB/Engine/FileReader/FileReaderV7.cs b/LiteDB/Engine/FileReader/FileReaderV7.cs index ec10ddbfa..7a8267275 100644 --- a/LiteDB/Engine/FileReader/FileReaderV7.cs +++ b/LiteDB/Engine/FileReader/FileReaderV7.cs @@ -25,6 +25,7 @@ internal class FileReaderV7 : IFileReader private byte[] _buffer = new byte[V7_PAGE_SIZE]; private bool _disposedValue; + private static readonly byte[] arrayByteEmpty = new byte[0]; public IDictionary GetPragmas() => new Dictionary() { @@ -387,7 +388,7 @@ private byte[] ReadExtendData(uint extendPageID) { var page = this.ReadPage(extendPageID); - if (page["pageType"].AsInt32 != 5) return new byte[0]; + if (page["pageType"].AsInt32 != 5) return arrayByteEmpty; buffer.Write(page["data"].AsBinary, 0, page["itemCount"].AsInt32); @@ -403,10 +404,14 @@ private byte[] ReadExtendData(uint extendPageID) /// private HashSet VisitIndexPages(uint startPageID) { - var toVisit = new HashSet(new uint[] { startPageID }); + var toVisit = new HashSet + { + startPageID + }; + var visited = new HashSet(); - while(toVisit.Count > 0) + while (toVisit.Count > 0) { var indexPageID = toVisit.First(); diff --git a/LiteDB/Engine/FileReader/Legacy/AesEncryption.cs b/LiteDB/Engine/FileReader/Legacy/AesEncryption.cs index 1c18cf89d..5e7f9601b 100644 --- a/LiteDB/Engine/FileReader/Legacy/AesEncryption.cs +++ b/LiteDB/Engine/FileReader/Legacy/AesEncryption.cs @@ -58,7 +58,7 @@ public byte[] Decrypt(byte[] encryptedValue, int offset = 0, int count = -1) crypto.Write(encryptedValue, offset, count == -1 ? encryptedValue.Length : count); crypto.FlushFinalBlock(); stream.Position = 0; - var decryptedBytes = new Byte[stream.Length]; + var decryptedBytes = new byte[stream.Length]; stream.Read(decryptedBytes, 0, decryptedBytes.Length); return decryptedBytes; diff --git a/LiteDB/Engine/FileReader/Legacy/ByteReader.cs b/LiteDB/Engine/FileReader/Legacy/ByteReader.cs index 2fcae686b..730da51b3 100644 --- a/LiteDB/Engine/FileReader/Legacy/ByteReader.cs +++ b/LiteDB/Engine/FileReader/Legacy/ByteReader.cs @@ -103,7 +103,7 @@ public Decimal ReadDecimal() return new Decimal(new int[] { a, b, c, d }); } - public Byte[] ReadBytes(int count) + public byte[] ReadBytes(int count) { var buffer = new byte[count]; diff --git a/LiteDB/Engine/Sort/SortContainer.cs b/LiteDB/Engine/Sort/SortContainer.cs index 2e5e7b313..9705f5afa 100644 --- a/LiteDB/Engine/Sort/SortContainer.cs +++ b/LiteDB/Engine/Sort/SortContainer.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; @@ -26,6 +27,8 @@ internal class SortContainer : IDisposable private BufferReader _reader = null; + private static readonly ArrayPool _bufferPool = ArrayPool.Shared; + /// /// Returns if current container has no more items to read /// @@ -119,7 +122,7 @@ public bool MoveNext() /// private IEnumerable GetSourceFromStream(Stream stream) { - var bytes = BufferPool.Rent(PAGE_SIZE); + var bytes = _bufferPool.Rent(PAGE_SIZE); var buffer = new BufferSlice(bytes, 0, PAGE_SIZE); while (_readPosition < _size) @@ -133,7 +136,7 @@ private IEnumerable GetSourceFromStream(Stream stream) yield return buffer; } - BufferPool.Return(bytes); + _bufferPool.Return(bytes, true); } public void Dispose() diff --git a/LiteDB/Engine/Sort/SortService.cs b/LiteDB/Engine/Sort/SortService.cs index e52649e0b..2ec04a0c2 100644 --- a/LiteDB/Engine/Sort/SortService.cs +++ b/LiteDB/Engine/Sort/SortService.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; @@ -30,6 +31,8 @@ internal class SortService : IDisposable private readonly BufferSlice _buffer; private readonly Lazy _reader; + private static readonly ArrayPool _bufferPool = ArrayPool.Shared; + /// /// Get how many documents was inserted by Insert method /// @@ -49,7 +52,7 @@ public SortService(SortDisk disk, int order, EnginePragmas pragmas) _reader = new Lazy(() => _disk.GetReader()); - var bytes = BufferPool.Rent(disk.ContainerSize); + var bytes = new byte [disk.ContainerSize]; _buffer = new BufferSlice(bytes, 0, _containerSize); } @@ -68,9 +71,6 @@ public void Dispose() } } - // return array buffer into pool - BufferPool.Return(_buffer.Array); - // return open strem into disk if (_reader.IsValueCreated) { diff --git a/LiteDB/Engine/Structures/IndexNode.cs b/LiteDB/Engine/Structures/IndexNode.cs index 455df0f13..e21574bf6 100644 --- a/LiteDB/Engine/Structures/IndexNode.cs +++ b/LiteDB/Engine/Structures/IndexNode.cs @@ -28,6 +28,8 @@ internal class IndexNode private readonly IndexPage _page; private readonly BufferSlice _segment; + private static readonly byte[] arrayByteEmpty = new byte[0]; + /// /// Position of this node inside a IndexPage (not persist) /// @@ -164,7 +166,7 @@ public IndexNode(IndexPage page, byte index, BufferSlice segment, byte slot, byt public IndexNode(BsonDocument doc) { _page = null; - _segment = new BufferSlice(new byte[0], 0, 0); + _segment = new BufferSlice(arrayByteEmpty, 0, 0); this.Position = new PageAddress(0, 0); this.Slot = 0; diff --git a/LiteDB/LiteDB.csproj b/LiteDB/LiteDB.csproj index 535aca532..86ec3afdd 100644 --- a/LiteDB/LiteDB.csproj +++ b/LiteDB/LiteDB.csproj @@ -66,6 +66,10 @@ + + + + diff --git a/LiteDB/Utils/Pool/ArrayPool.cs b/LiteDB/Utils/Pool/ArrayPool.cs deleted file mode 100644 index 2532f0ab5..000000000 --- a/LiteDB/Utils/Pool/ArrayPool.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using static LiteDB.Constants; - -namespace LiteDB -{ - internal class ArrayPool - { - private static readonly T[] Emptry = new T[0]; - - private const int SLOT_COUNT = 8; - - private readonly SlotBuff[] _buckets; - - public ArrayPool() - { - _buckets = new SlotBuff[BucketHelper.BucketCount]; - for (var i = 0; i < BucketHelper.BucketCount; ++i) - { - var maxSlotSize = BucketHelper.GetMaxSizeForBucket(i); - - var slotBuff = new SlotBuff(); - _buckets[i] = slotBuff; - - for (var j = 0; j < SLOT_COUNT; ++j) - { - if (!slotBuff.TryPush(new T[maxSlotSize])) - { - throw new InvalidOperationException(); - } - } - } - } - - public T[] Rent(int minSize) - { - if (minSize < 0) - { - throw new ArgumentOutOfRangeException(nameof(minSize)); - } - - if (minSize == 0) - { - return Emptry; - } - - var bucketIdx = BucketHelper.GetBucketIndex(minSize); - if (bucketIdx < 0) - { - return new T[minSize]; - } - - var buff = _buckets[bucketIdx]; - var returnBuff = buff.TryPop(); - - if (returnBuff != null) - { - return returnBuff; - } - - return new T[minSize]; - } - - public void Return(T[] buff) - { - if (buff == null) - { - throw new ArgumentNullException(nameof(buff)); - } - - if (buff.Length == 0) - { - return; - } - - var bucketIndex = BucketHelper.GetBucketIndex(buff.Length); - if (bucketIndex < 0) - return; - - var buffer = _buckets[bucketIndex]; - buffer.TryPush(buff); - } - - private sealed class SlotBuff - { - private readonly T[][] _buff = new T[SLOT_COUNT][]; - private int _size; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryPush(T[] item) - { - if (_size >= SLOT_COUNT) - return false; - - _buff[_size++] = item; - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T[] TryPop() - { - if (_size <= 0) - return null; - - var arr = _buff[--_size]; - _buff[_size] = null; - return arr; - } - } - } -} \ No newline at end of file