diff --git a/src/Neo.Extensions/ByteArrayEqualityComparer.cs b/src/Neo.Extensions/ByteArrayEqualityComparer.cs index 50861083b2..091185bd02 100644 --- a/src/Neo.Extensions/ByteArrayEqualityComparer.cs +++ b/src/Neo.Extensions/ByteArrayEqualityComparer.cs @@ -11,13 +11,15 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace Neo.Extensions { public class ByteArrayEqualityComparer : IEqualityComparer { - public static readonly ByteArrayEqualityComparer Default = new(); + public static readonly ByteArrayEqualityComparer Instance = new(); + /// public bool Equals(byte[]? x, byte[]? y) { if (ReferenceEquals(x, y)) return true; @@ -26,9 +28,12 @@ public bool Equals(byte[]? x, byte[]? y) return x.AsSpan().SequenceEqual(y.AsSpan()); } - public int GetHashCode(byte[] obj) - { - return obj.XxHash3_32(); - } + /// + public int GetHashCode([DisallowNull] byte[] obj) => + obj.XxHash3_32(); + + /// + public int GetHashCode([DisallowNull] object obj) => + obj is byte[] b ? GetHashCode(b) : 0; } } diff --git a/src/Neo/IO/Caching/ECPointCache.cs b/src/Neo/IO/Caching/ECPointCache.cs index f678e58edd..36db808f24 100644 --- a/src/Neo/IO/Caching/ECPointCache.cs +++ b/src/Neo/IO/Caching/ECPointCache.cs @@ -17,7 +17,7 @@ namespace Neo.IO.Caching internal class ECPointCache : FIFOCache { public ECPointCache(int max_capacity) - : base(max_capacity, ByteArrayEqualityComparer.Default) + : base(max_capacity, ByteArrayEqualityComparer.Instance) { } diff --git a/src/Neo/Persistence/MemorySnapshot.cs b/src/Neo/Persistence/MemorySnapshot.cs index 2da2ec2849..53519795dc 100644 --- a/src/Neo/Persistence/MemorySnapshot.cs +++ b/src/Neo/Persistence/MemorySnapshot.cs @@ -35,8 +35,8 @@ internal MemorySnapshot(MemoryStore store, ConcurrentDictionary { Store = store; _innerData = innerData; - _immutableData = innerData.ToImmutableDictionary(ByteArrayEqualityComparer.Default); - _writeBatch = new ConcurrentDictionary(ByteArrayEqualityComparer.Default); + _immutableData = innerData.ToImmutableDictionary(ByteArrayEqualityComparer.Instance); + _writeBatch = new ConcurrentDictionary(ByteArrayEqualityComparer.Instance); } public void Commit() @@ -64,16 +64,21 @@ public void Put(byte[] key, byte[] value) public IEnumerable<(byte[] Key, byte[] Value)> Seek(byte[]? keyOrPrefix, SeekDirection direction = SeekDirection.Forward) { keyOrPrefix ??= []; + if (direction == SeekDirection.Backward && keyOrPrefix.Length == 0) yield break; var comparer = direction == SeekDirection.Forward ? ByteArrayComparer.Default : ByteArrayComparer.Reverse; + IEnumerable> records = _immutableData; + if (keyOrPrefix.Length > 0) records = records .Where(p => comparer.Compare(p.Key, keyOrPrefix) >= 0); + records = records.OrderBy(p => p.Key, comparer); + foreach (var pair in records) - yield return (pair.Key[..], pair.Value[..]); + yield return new(pair.Key[..], pair.Value[..]); } public byte[]? TryGet(byte[] key) diff --git a/src/Neo/Persistence/MemoryStore.cs b/src/Neo/Persistence/MemoryStore.cs index 796d982569..9e0cc2702e 100644 --- a/src/Neo/Persistence/MemoryStore.cs +++ b/src/Neo/Persistence/MemoryStore.cs @@ -25,7 +25,7 @@ namespace Neo.Persistence /// public class MemoryStore : IStore { - private readonly ConcurrentDictionary _innerData = new(ByteArrayEqualityComparer.Default); + private readonly ConcurrentDictionary _innerData = new(ByteArrayEqualityComparer.Instance); [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Delete(byte[] key) @@ -51,16 +51,19 @@ public void Put(byte[] key, byte[] value) public IEnumerable<(byte[] Key, byte[] Value)> Seek(byte[]? keyOrPrefix, SeekDirection direction = SeekDirection.Forward) { keyOrPrefix ??= []; - if (direction == SeekDirection.Backward && keyOrPrefix.Length == 0) yield break; + if (direction == SeekDirection.Backward && keyOrPrefix.Length == 0) yield break; var comparer = direction == SeekDirection.Forward ? ByteArrayComparer.Default : ByteArrayComparer.Reverse; + IEnumerable> records = _innerData; + if (keyOrPrefix.Length > 0) records = records .Where(p => comparer.Compare(p.Key, keyOrPrefix) >= 0); records = records.OrderBy(p => p.Key, comparer); + foreach (var pair in records) - yield return (pair.Key[..], pair.Value[..]); + yield return new(pair.Key[..], pair.Value[..]); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/Neo/SmartContract/KeyBuilder.cs b/src/Neo/SmartContract/KeyBuilder.cs index 84e7d07183..4a6154ec63 100644 --- a/src/Neo/SmartContract/KeyBuilder.cs +++ b/src/Neo/SmartContract/KeyBuilder.cs @@ -11,10 +11,10 @@ #nullable enable +using Neo.Extensions; using Neo.IO; using System; using System.Buffers.Binary; -using System.IO; using System.Runtime.CompilerServices; namespace Neo.SmartContract @@ -24,7 +24,8 @@ namespace Neo.SmartContract /// public class KeyBuilder { - private readonly MemoryStream _stream; + private readonly Memory _cachedData; + private int _keyLen = 0; /// /// Initializes a new instance of the class. @@ -34,12 +35,11 @@ public class KeyBuilder /// The hint of the storage key size(including the id and prefix). public KeyBuilder(int id, byte prefix, int keySizeHint = ApplicationEngine.MaxStorageKeySize) { - Span data = stackalloc byte[sizeof(int)]; - BinaryPrimitives.WriteInt32LittleEndian(data, id); + _cachedData = new byte[keySizeHint]; + BinaryPrimitives.WriteInt32LittleEndian(_cachedData.Span, id); - _stream = new(keySizeHint); - _stream.Write(data); - _stream.WriteByte(prefix); + _keyLen = sizeof(int); + _cachedData.Span[_keyLen++] = prefix; } /// @@ -50,7 +50,7 @@ public KeyBuilder(int id, byte prefix, int keySizeHint = ApplicationEngine.MaxSt [MethodImpl(MethodImplOptions.AggressiveInlining)] public KeyBuilder Add(byte key) { - _stream.WriteByte(key); + _cachedData.Span[_keyLen++] = key; return this; } @@ -62,7 +62,8 @@ public KeyBuilder Add(byte key) [MethodImpl(MethodImplOptions.AggressiveInlining)] public KeyBuilder Add(ReadOnlySpan key) { - _stream.Write(key); + key.CopyTo(_cachedData.Span[_keyLen..]); + _keyLen += key.Length; return this; } @@ -97,11 +98,11 @@ public KeyBuilder Add(ReadOnlySpan key) /// A reference to this instance after the add operation has completed. public KeyBuilder Add(ISerializable key) { - using (BinaryWriter writer = new(_stream, Utility.StrictUTF8, true)) - { - key.Serialize(writer); - writer.Flush(); - } + var raw = key.ToArray(); + + raw.CopyTo(_cachedData[_keyLen..]); + _keyLen += raw.Length; + return this; } @@ -167,15 +168,12 @@ public KeyBuilder AddBigEndian(ulong key) /// The storage key. public byte[] ToArray() { - using (_stream) - { - return _stream.ToArray(); - } + return _cachedData[.._keyLen].ToArray(); } public static implicit operator StorageKey(KeyBuilder builder) { - return new StorageKey(builder.ToArray()); + return new StorageKey(builder._cachedData[..builder._keyLen].ToArray()); } } } diff --git a/src/Neo/SmartContract/StorageItem.cs b/src/Neo/SmartContract/StorageItem.cs index 6b291edc8d..ed7f817843 100644 --- a/src/Neo/SmartContract/StorageItem.cs +++ b/src/Neo/SmartContract/StorageItem.cs @@ -47,7 +47,7 @@ public ReadOnlyMemory Value } set { - _value = value; + _value = value.ToArray(); // create new memory region _cache = null; } } @@ -63,7 +63,7 @@ public StorageItem() { } /// The byte array value of the . public StorageItem(byte[] value) { - _value = value; + _value = value.AsMemory().ToArray(); // allocate new buffer } /// @@ -99,11 +99,13 @@ public void Add(BigInteger integer) /// The created . public StorageItem Clone() { - return new() + var newItem = new StorageItem { - _value = _value, - _cache = _cache is IInteroperable interoperable ? interoperable.Clone() : _cache + _value = _value.ToArray(), // allocate new buffer + _cache = _cache is IInteroperable interoperable ? interoperable.Clone() : _cache, }; + + return newItem; } public void Deserialize(ref MemoryReader reader) @@ -117,7 +119,7 @@ public void Deserialize(ref MemoryReader reader) /// The instance to be copied. public void FromReplica(StorageItem replica) { - _value = replica._value; + _value = replica._value.ToArray(); // allocate new buffer. DONT USE INSTANCE if (replica._cache is IInteroperable interoperable) { if (_cache?.GetType() == interoperable.GetType()) @@ -144,7 +146,7 @@ public void FromReplica(StorageItem replica) interoperable.FromStackItem(BinarySerializer.Deserialize(_value, ExecutionEngineLimits.Default)); _cache = interoperable; } - _value = null; + _value = ReadOnlyMemory.Empty; // garbage collect the previous allocated memory space return (T)_cache; } @@ -162,7 +164,7 @@ public void FromReplica(StorageItem replica) interoperable.FromStackItem(BinarySerializer.Deserialize(_value, ExecutionEngineLimits.Default), verify); _cache = interoperable; } - _value = null; + _value = ReadOnlyMemory.Empty; // garbage collect the previous allocated memory space return (T)_cache; } @@ -178,7 +180,7 @@ public void Serialize(BinaryWriter writer) public void Set(BigInteger integer) { _cache = integer; - _value = null; + _value = ReadOnlyMemory.Empty; // garbage collect the previous allocated memory space } /// @@ -188,7 +190,7 @@ public void Set(BigInteger integer) public void Set(IInteroperable interoperable) { _cache = interoperable; - _value = null; + _value = ReadOnlyMemory.Empty; // garbage collect the previous allocated memory space } public static implicit operator BigInteger(StorageItem item) @@ -199,12 +201,12 @@ public static implicit operator BigInteger(StorageItem item) public static implicit operator StorageItem(BigInteger value) { - return new StorageItem(value); + return new(value); } public static implicit operator StorageItem(byte[] value) { - return new StorageItem(value); + return new(value); } } } diff --git a/src/Neo/SmartContract/StorageKey.cs b/src/Neo/SmartContract/StorageKey.cs index 885898c6dc..bdb053a984 100644 --- a/src/Neo/SmartContract/StorageKey.cs +++ b/src/Neo/SmartContract/StorageKey.cs @@ -33,7 +33,7 @@ public sealed record StorageKey /// public ReadOnlyMemory Key { get; init; } - private byte[]? _cache; + private Memory _cache; // NOTE: StorageKey is readonly, so we can cache the hash code. private int _hashCode = 0; @@ -49,9 +49,9 @@ public StorageKey() /// The cached byte array. NOTE: It must be read-only and can be modified by the caller. internal StorageKey(byte[] cache) { - _cache = cache; - Id = BinaryPrimitives.ReadInt32LittleEndian(cache); - Key = cache.AsMemory(sizeof(int)); + _cache = cache.AsMemory().ToArray(); // allocate new buffer + Id = BinaryPrimitives.ReadInt32LittleEndian(_cache.Span); + Key = _cache[sizeof(int)..].ToArray(); // allocate new buffer. NOTE: DONT USE POINTERS HERE } /// @@ -68,10 +68,10 @@ internal StorageKey(ReadOnlySpan cache) : this(cache.ToArray()) { } /// The created search prefix. public static byte[] CreateSearchPrefix(int id, ReadOnlySpan prefix) { - var buffer = new byte[sizeof(int) + prefix.Length]; + Span buffer = stackalloc byte[sizeof(int) + prefix.Length]; BinaryPrimitives.WriteInt32LittleEndian(buffer, id); - prefix.CopyTo(buffer.AsSpan(sizeof(int))); - return buffer; + prefix.CopyTo(buffer[sizeof(int)..]); + return buffer.ToArray(); } public bool Equals(StorageKey? other) @@ -92,22 +92,22 @@ public override int GetHashCode() public byte[] ToArray() { - if (_cache is null) + if (_cache is { IsEmpty: true }) { - _cache = new byte[sizeof(int) + Key.Length]; - BinaryPrimitives.WriteInt32LittleEndian(_cache, Id); - Key.CopyTo(_cache.AsMemory(sizeof(int))); + _cache = new byte[sizeof(int) + Key.Length]; // allocate new buffer + BinaryPrimitives.WriteInt32LittleEndian(_cache.Span, Id); + Key.CopyTo(_cache[sizeof(int)..]); } - return _cache; + return _cache.ToArray(); // allocate new buffer } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator StorageKey(byte[] value) => new(value); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator StorageKey(ReadOnlyMemory value) => new(value.Span); + public static implicit operator StorageKey(ReadOnlyMemory value) => new(value.ToArray()); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator StorageKey(ReadOnlySpan value) => new(value); + public static implicit operator StorageKey(ReadOnlySpan value) => new(value.ToArray()); } } diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Proof.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Proof.cs index 656ab36d78..76cb95336e 100644 --- a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Proof.cs +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Proof.cs @@ -25,7 +25,7 @@ public bool TryGetProof(byte[] key, out HashSet proof) throw new ArgumentException("could not be empty", nameof(key)); if (path.Length > Node.MaxKeyLength) throw new ArgumentException("exceeds limit", nameof(key)); - proof = new HashSet(ByteArrayEqualityComparer.Default); + proof = new HashSet(ByteArrayEqualityComparer.Instance); return GetProof(ref root, path, proof); } diff --git a/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Trie.cs b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Trie.cs index 3383d12f3c..9963237312 100644 --- a/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Trie.cs +++ b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Trie.cs @@ -21,7 +21,7 @@ namespace Neo.Cryptography.MPTTrie.Tests { class TestSnapshot : IStoreSnapshot { - public Dictionary store = new Dictionary(ByteArrayEqualityComparer.Default); + public Dictionary store = new Dictionary(ByteArrayEqualityComparer.Instance); private byte[] StoreKey(byte[] key) { diff --git a/tests/Neo.Extensions.Tests/UT_ByteArrayEqualityComparer.cs b/tests/Neo.Extensions.Tests/UT_ByteArrayEqualityComparer.cs index 81d3034081..7467f520b0 100644 --- a/tests/Neo.Extensions.Tests/UT_ByteArrayEqualityComparer.cs +++ b/tests/Neo.Extensions.Tests/UT_ByteArrayEqualityComparer.cs @@ -22,7 +22,7 @@ public void TestEqual() { var a = new byte[] { 1, 2, 3, 4, 1, 2, 3, 4, 5 }; var b = new byte[] { 1, 2, 3, 4, 1, 2, 3, 4, 5 }; - var check = ByteArrayEqualityComparer.Default; + var check = ByteArrayEqualityComparer.Instance; Assert.IsTrue(check.Equals(a, a)); Assert.IsTrue(check.Equals(a, b)); @@ -45,7 +45,7 @@ public void TestGetHashCode() { var a = new byte[] { 1, 2, 3, 4, 1, 2, 3, 4, 5 }; var b = new byte[] { 1, 2, 3, 4, 1, 2, 3, 4, 5 }; - var check = ByteArrayEqualityComparer.Default; + var check = ByteArrayEqualityComparer.Instance; Assert.AreEqual(check.GetHashCode(a), check.GetHashCode(b)); Assert.AreNotEqual(check.GetHashCode(a), check.GetHashCode(b.Take(8).ToArray()));