From 6bd0886d95031f61da5a96081238c3e3b349da51 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Tue, 26 Nov 2024 00:11:46 +0800 Subject: [PATCH 1/6] Optimize KeyBuilder and Add some UTs for not-covered methods --- benchmarks/Neo.Benchmarks/Program.cs | 2 + .../SmartContract/Benchmarks.StorageKey.cs | 50 +++++++++++++++++++ src/Neo/SmartContract/KeyBuilder.cs | 23 ++++++--- .../SmartContract/UT_KeyBuilder.cs | 35 +++++++++++++ 4 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 benchmarks/Neo.Benchmarks/SmartContract/Benchmarks.StorageKey.cs diff --git a/benchmarks/Neo.Benchmarks/Program.cs b/benchmarks/Neo.Benchmarks/Program.cs index a64a1ca981..0bee186889 100644 --- a/benchmarks/Neo.Benchmarks/Program.cs +++ b/benchmarks/Neo.Benchmarks/Program.cs @@ -11,7 +11,9 @@ using BenchmarkDotNet.Running; using Neo.Benchmark; +using Neo.SmartContract.Benchmark; // BenchmarkRunner.Run(); BenchmarkRunner.Run(); BenchmarkRunner.Run(); +BenchmarkRunner.Run(); diff --git a/benchmarks/Neo.Benchmarks/SmartContract/Benchmarks.StorageKey.cs b/benchmarks/Neo.Benchmarks/SmartContract/Benchmarks.StorageKey.cs new file mode 100644 index 0000000000..a40eab11ed --- /dev/null +++ b/benchmarks/Neo.Benchmarks/SmartContract/Benchmarks.StorageKey.cs @@ -0,0 +1,50 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Benchmarks.StorageKey.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using BenchmarkDotNet.Attributes; +using System.Text; + +namespace Neo.SmartContract.Benchmark +{ + public class Benchmarks_StorageKey + { + // for avoiding overhead of encoding + private static readonly byte[] testBytes = Encoding.ASCII.GetBytes("StorageKey"); + + private const int prefixSize = sizeof(int) + sizeof(byte); + + [Benchmark] + public void KeyBuilder_AddInt() + { + var key = new KeyBuilder(1, 0) + .AddBigEndian(1) + .AddBigEndian(2) + .AddBigEndian(3); + + var bytes = key.ToArray(); + if (bytes.Length != prefixSize + 3 * sizeof(int)) + throw new InvalidOperationException(); + } + + [Benchmark] + public void KeyBuilder_AddBytes() + { + var key = new KeyBuilder(1, 0) + .Add(testBytes) + .Add(testBytes) + .Add(testBytes); + + var bytes = key.ToArray(); + if (bytes.Length != prefixSize + 3 * testBytes.Length) + throw new InvalidOperationException(); + } + } +} diff --git a/src/Neo/SmartContract/KeyBuilder.cs b/src/Neo/SmartContract/KeyBuilder.cs index cfd27cc6f8..64089f901d 100644 --- a/src/Neo/SmartContract/KeyBuilder.cs +++ b/src/Neo/SmartContract/KeyBuilder.cs @@ -13,6 +13,7 @@ using System; using System.Buffers.Binary; using System.IO; +using System.Runtime.CompilerServices; namespace Neo.SmartContract { @@ -21,18 +22,20 @@ namespace Neo.SmartContract /// public class KeyBuilder { - private readonly MemoryStream stream = new(); + private readonly MemoryStream stream; /// /// Initializes a new instance of the class. /// /// The id of the contract. /// The prefix of the key. - public KeyBuilder(int id, byte prefix) + /// The hint of the storage key size. + public KeyBuilder(int id, byte prefix, int keySizeHint = ApplicationEngine.MaxStorageKeySize) { - var data = new byte[sizeof(int)]; + Span data = stackalloc byte[sizeof(int)]; BinaryPrimitives.WriteInt32LittleEndian(data, id); + stream = new(keySizeHint); stream.Write(data); stream.WriteByte(prefix); } @@ -42,6 +45,7 @@ public KeyBuilder(int id, byte prefix) /// /// Part of the key. /// A reference to this instance after the add operation has completed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public KeyBuilder Add(byte key) { stream.WriteByte(key); @@ -53,6 +57,7 @@ public KeyBuilder Add(byte key) /// /// Part of the key. /// A reference to this instance after the add operation has completed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public KeyBuilder Add(ReadOnlySpan key) { stream.Write(key); @@ -79,9 +84,10 @@ public KeyBuilder Add(ISerializable key) /// /// Part of the key. /// A reference to this instance after the add operation has completed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public KeyBuilder AddBigEndian(int key) { - var data = new byte[sizeof(int)]; + Span data = stackalloc byte[sizeof(int)]; BinaryPrimitives.WriteInt32BigEndian(data, key); return Add(data); @@ -92,9 +98,10 @@ public KeyBuilder AddBigEndian(int key) /// /// Part of the key. /// A reference to this instance after the add operation has completed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public KeyBuilder AddBigEndian(uint key) { - var data = new byte[sizeof(uint)]; + Span data = stackalloc byte[sizeof(uint)]; BinaryPrimitives.WriteUInt32BigEndian(data, key); return Add(data); @@ -105,9 +112,10 @@ public KeyBuilder AddBigEndian(uint key) /// /// Part of the key. /// A reference to this instance after the add operation has completed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public KeyBuilder AddBigEndian(long key) { - var data = new byte[sizeof(long)]; + Span data = stackalloc byte[sizeof(long)]; BinaryPrimitives.WriteInt64BigEndian(data, key); return Add(data); @@ -118,9 +126,10 @@ public KeyBuilder AddBigEndian(long key) /// /// Part of the key. /// A reference to this instance after the add operation has completed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public KeyBuilder AddBigEndian(ulong key) { - var data = new byte[sizeof(ulong)]; + Span data = stackalloc byte[sizeof(ulong)]; BinaryPrimitives.WriteUInt64BigEndian(data, key); return Add(data); diff --git a/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs b/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs index a3514c7f5a..2be95400b7 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs @@ -42,5 +42,40 @@ public void Test() key = key.AddBigEndian(1); Assert.AreEqual("010000000000000001", key.ToArray().ToHexString()); } + + [TestMethod] + public void TestAdd() + { + var key = new KeyBuilder(1, 2); + Assert.AreEqual("0100000002", key.ToArray().ToHexString()); + + // add int + key = new KeyBuilder(1, 2); + key = key.AddBigEndian(-1); + key = key.AddBigEndian(2); + key = key.AddBigEndian(3); + Assert.AreEqual("0100000002ffffffff0000000200000003", key.ToArray().ToHexString()); + + // add ulong + key = new KeyBuilder(1, 2); + key = key.AddBigEndian(1ul); + key = key.AddBigEndian(2ul); + key = key.AddBigEndian(ulong.MaxValue); + Assert.AreEqual("010000000200000000000000010000000000000002ffffffffffffffff", key.ToArray().ToHexString()); + + // add uint + key = new KeyBuilder(1, 2); + key = key.AddBigEndian(1u); + key = key.AddBigEndian(2u); + key = key.AddBigEndian(uint.MaxValue); + Assert.AreEqual("01000000020000000100000002ffffffff", key.ToArray().ToHexString()); + + // add byte + key = new KeyBuilder(1, 2); + key = key.Add((byte)1); + key = key.Add((byte)2); + key = key.Add((byte)3); + Assert.AreEqual("0100000002010203", key.ToArray().ToHexString()); + } } } From daf8b59a23f675805e795441f27ef666f4148646 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Tue, 26 Nov 2024 01:20:22 +0800 Subject: [PATCH 2/6] optimization for adding UInt160 and UInt256 to KeyBuilder --- .../SmartContract/Benchmarks.StorageKey.cs | 12 +++++++ src/Neo/SmartContract/KeyBuilder.cs | 24 +++++++++++++ src/Neo/UInt160.cs | 10 ++++++ src/Neo/UInt256.cs | 11 ++++++ .../SmartContract/UT_KeyBuilder.cs | 34 ++++++++++++++++++- 5 files changed, 90 insertions(+), 1 deletion(-) diff --git a/benchmarks/Neo.Benchmarks/SmartContract/Benchmarks.StorageKey.cs b/benchmarks/Neo.Benchmarks/SmartContract/Benchmarks.StorageKey.cs index a40eab11ed..d55db1e5cd 100644 --- a/benchmarks/Neo.Benchmarks/SmartContract/Benchmarks.StorageKey.cs +++ b/benchmarks/Neo.Benchmarks/SmartContract/Benchmarks.StorageKey.cs @@ -46,5 +46,17 @@ public void KeyBuilder_AddBytes() if (bytes.Length != prefixSize + 3 * testBytes.Length) throw new InvalidOperationException(); } + + [Benchmark] + public void KeyBuilder_AddUInt160() + { + Span value = stackalloc byte[UInt160.Length]; + var key = new KeyBuilder(1, 0) + .Add(new UInt160(value)); + + var bytes = key.ToArray(); + if (bytes.Length != prefixSize + UInt160.Length) + throw new InvalidOperationException(); + } } } diff --git a/src/Neo/SmartContract/KeyBuilder.cs b/src/Neo/SmartContract/KeyBuilder.cs index 64089f901d..1192bde36b 100644 --- a/src/Neo/SmartContract/KeyBuilder.cs +++ b/src/Neo/SmartContract/KeyBuilder.cs @@ -64,6 +64,30 @@ public KeyBuilder Add(ReadOnlySpan key) return this; } + /// + /// Adds part of the key to the builder. + /// + /// Part of the key represented by a byte array. + /// A reference to this instance after the add operation has completed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public KeyBuilder Add(byte[] key) => Add(key.AsSpan()); + + /// + /// Adds part of the key to the builder. + /// + /// Part of the key represented by a . + /// A reference to this instance after the add operation has completed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public KeyBuilder Add(UInt160 key) => Add(key.GetSpan()); + + /// + /// Adds part of the key to the builder. + /// + /// Part of the key represented by a . + /// A reference to this instance after the add operation has completed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public KeyBuilder Add(UInt256 key) => Add(key.GetSpan()); + /// /// Adds part of the key to the builder. /// diff --git a/src/Neo/UInt160.cs b/src/Neo/UInt160.cs index da4c920cbe..95ce83b181 100644 --- a/src/Neo/UInt160.cs +++ b/src/Neo/UInt160.cs @@ -101,6 +101,16 @@ public override int GetHashCode() return HashCode.Combine(_value1, _value2, _value3); } + /// + /// Gets a ReadOnlySpan that represents the current value in little-endian. + /// + /// A ReadOnlySpan that represents the current value in little-endian. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan GetSpan() + { + return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref _value1), Length); + } + /// /// Parses an from the specified . /// diff --git a/src/Neo/UInt256.cs b/src/Neo/UInt256.cs index 95324ef6ac..87c5cf684b 100644 --- a/src/Neo/UInt256.cs +++ b/src/Neo/UInt256.cs @@ -14,6 +14,7 @@ using System; using System.Globalization; using System.IO; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Neo @@ -101,6 +102,16 @@ public override int GetHashCode() return (int)value1; } + /// + /// Gets a ReadOnlySpan that represents the current value in little-endian. + /// + /// A ReadOnlySpan that represents the current value in little-endian. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan GetSpan() + { + return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref value1), Length); + } + /// /// Parses an from the specified . /// diff --git a/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs b/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs index 2be95400b7..029514c103 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs @@ -11,6 +11,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Extensions; +using Neo.IO; using Neo.SmartContract; namespace Neo.UnitTests.SmartContract @@ -44,7 +45,7 @@ public void Test() } [TestMethod] - public void TestAdd() + public void TestAddInt() { var key = new KeyBuilder(1, 2); Assert.AreEqual("0100000002", key.ToArray().ToHexString()); @@ -77,5 +78,36 @@ public void TestAdd() key = key.Add((byte)3); Assert.AreEqual("0100000002010203", key.ToArray().ToHexString()); } + + [TestMethod] + public void TestAddUInt() + { + var key = new KeyBuilder(1, 2); + var value = new byte[UInt160.Length]; + for (int i = 0; i < value.Length; i++) + value[i] = (byte)i; + + key = key.Add(new UInt160(value)); + Assert.AreEqual("0100000002000102030405060708090a0b0c0d0e0f10111213", key.ToArray().ToHexString()); + + var key2 = new KeyBuilder(1, 2); + key2 = key2.Add((ISerializable)(new UInt160(value))); + + // It must be same before and after optimization. + Assert.AreEqual(key.ToArray().ToHexString(), key2.ToArray().ToHexString()); + + key = new KeyBuilder(1, 2); + value = new byte[UInt256.Length]; + for (int i = 0; i < value.Length; i++) + value[i] = (byte)i; + key = key.Add(new UInt256(value)); + Assert.AreEqual("0100000002000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", key.ToArray().ToHexString()); + + key2 = new KeyBuilder(1, 2); + key2 = key2.Add((ISerializable)(new UInt256(value))); + + // It must be same before and after optimization. + Assert.AreEqual(key.ToArray().ToHexString(), key2.ToArray().ToHexString()); + } } } From 0ec89bf74dff47d59ff22c033eeba220cd1a86cb Mon Sep 17 00:00:00 2001 From: nan01ab Date: Tue, 26 Nov 2024 01:26:58 +0800 Subject: [PATCH 3/6] fix stream capacity calculation --- src/Neo/SmartContract/KeyBuilder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Neo/SmartContract/KeyBuilder.cs b/src/Neo/SmartContract/KeyBuilder.cs index 1192bde36b..429b8d4843 100644 --- a/src/Neo/SmartContract/KeyBuilder.cs +++ b/src/Neo/SmartContract/KeyBuilder.cs @@ -29,13 +29,13 @@ public class KeyBuilder /// /// The id of the contract. /// The prefix of the key. - /// The hint of the storage key size. + /// The hint of the storage key size(not 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); - stream = new(keySizeHint); + stream = new(sizeof(int) + sizeof(byte) + keySizeHint); stream.Write(data); stream.WriteByte(prefix); } From f761918747f5ef712f3bb6c9fda99f2878512906 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Tue, 26 Nov 2024 08:35:35 +0800 Subject: [PATCH 4/6] Update src/Neo/SmartContract/KeyBuilder.cs Co-authored-by: Shargon --- src/Neo/SmartContract/KeyBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo/SmartContract/KeyBuilder.cs b/src/Neo/SmartContract/KeyBuilder.cs index 429b8d4843..21c9e1114c 100644 --- a/src/Neo/SmartContract/KeyBuilder.cs +++ b/src/Neo/SmartContract/KeyBuilder.cs @@ -35,7 +35,7 @@ public KeyBuilder(int id, byte prefix, int keySizeHint = ApplicationEngine.MaxSt Span data = stackalloc byte[sizeof(int)]; BinaryPrimitives.WriteInt32LittleEndian(data, id); - stream = new(sizeof(int) + sizeof(byte) + keySizeHint); + stream = new(keySizeHint); stream.Write(data); stream.WriteByte(prefix); } From 7388709a7ddb0db75e3b143276bd112d95b2c5a4 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Tue, 26 Nov 2024 08:35:47 +0800 Subject: [PATCH 5/6] Update src/Neo/SmartContract/KeyBuilder.cs Co-authored-by: Shargon --- src/Neo/SmartContract/KeyBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo/SmartContract/KeyBuilder.cs b/src/Neo/SmartContract/KeyBuilder.cs index 21c9e1114c..9e6371ff31 100644 --- a/src/Neo/SmartContract/KeyBuilder.cs +++ b/src/Neo/SmartContract/KeyBuilder.cs @@ -29,7 +29,7 @@ public class KeyBuilder /// /// The id of the contract. /// The prefix of the key. - /// The hint of the storage key size(not including the id and prefix). + /// 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)]; From b8bcf2c90c7a9793efc82c99d8e5b6d1e5cb0ca1 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Tue, 26 Nov 2024 08:41:26 +0800 Subject: [PATCH 6/6] add more benchmark --- .../SmartContract/Benchmarks.StorageKey.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/benchmarks/Neo.Benchmarks/SmartContract/Benchmarks.StorageKey.cs b/benchmarks/Neo.Benchmarks/SmartContract/Benchmarks.StorageKey.cs index d55db1e5cd..5129a1dfaf 100644 --- a/benchmarks/Neo.Benchmarks/SmartContract/Benchmarks.StorageKey.cs +++ b/benchmarks/Neo.Benchmarks/SmartContract/Benchmarks.StorageKey.cs @@ -34,6 +34,19 @@ public void KeyBuilder_AddInt() throw new InvalidOperationException(); } + [Benchmark] + public void KeyBuilder_AddIntWithoutPrealloc() + { + var key = new KeyBuilder(1, 0, 0) + .AddBigEndian(1) + .AddBigEndian(2) + .AddBigEndian(3); + + var bytes = key.ToArray(); + if (bytes.Length != prefixSize + 3 * sizeof(int)) + throw new InvalidOperationException(); + } + [Benchmark] public void KeyBuilder_AddBytes() {