From bcc9f47e2e861566983312f6057d715b364f30b3 Mon Sep 17 00:00:00 2001 From: Michael Yarichuk Date: Sat, 2 May 2026 09:16:12 +0300 Subject: [PATCH] perf: add readonly qualifier on relevant struct methods --- src/SharpArena/Allocators/ArenaAllocator.cs | 4 +- src/SharpArena/Allocators/ArenaSegment.cs | 6 +- src/SharpArena/Collections/ArenaBlock.cs | 18 +- src/SharpArena/Collections/ArenaDictionary.cs | 42 ++-- src/SharpArena/Collections/ArenaList.cs | 18 +- src/SharpArena/Collections/ArenaSet.cs | 189 +++++++++++++----- src/SharpArena/Collections/Hashing.cs | 12 +- 7 files changed, 190 insertions(+), 99 deletions(-) diff --git a/src/SharpArena/Allocators/ArenaAllocator.cs b/src/SharpArena/Allocators/ArenaAllocator.cs index 2285882..435212c 100644 --- a/src/SharpArena/Allocators/ArenaAllocator.cs +++ b/src/SharpArena/Allocators/ArenaAllocator.cs @@ -158,8 +158,8 @@ public ArenaAllocator( seg->Size = segSize; seg->Base = mem + sizeof(ArenaSegment); #if DEBUG - seg->HeadCanary = ArenaSegment.Canary; - seg->TailCanary = ArenaSegment.Canary; + seg->HeadCanary = ArenaSegment.CANARY; + seg->TailCanary = ArenaSegment.CANARY; #endif return seg; } diff --git a/src/SharpArena/Allocators/ArenaSegment.cs b/src/SharpArena/Allocators/ArenaSegment.cs index b63144c..d44c6b6 100644 --- a/src/SharpArena/Allocators/ArenaSegment.cs +++ b/src/SharpArena/Allocators/ArenaSegment.cs @@ -20,7 +20,7 @@ public unsafe struct ArenaSegment /// public ulong TailCanary; - internal const ulong Canary = 0xDEADBEEFCAFEBABEul; + internal const ulong CANARY = 0xDEADBEEFCAFEBABEul; #endif /// @@ -51,8 +51,8 @@ public unsafe struct ArenaSegment public bool TryAlloc(nuint size, nuint align, out void* ptr) { #if DEBUG - Debug.Assert(HeadCanary == Canary, "Arena segment head canary corrupted"); - Debug.Assert(TailCanary == Canary, "Arena segment tail canary corrupted"); + Debug.Assert(HeadCanary == CANARY, "Arena segment head canary corrupted"); + Debug.Assert(TailCanary == CANARY, "Arena segment tail canary corrupted"); #endif if (align == 0) { diff --git a/src/SharpArena/Collections/ArenaBlock.cs b/src/SharpArena/Collections/ArenaBlock.cs index 0f9636a..546e7f8 100644 --- a/src/SharpArena/Collections/ArenaBlock.cs +++ b/src/SharpArena/Collections/ArenaBlock.cs @@ -92,10 +92,8 @@ public ArenaBlockList(ArenaAllocator arena, nuint blockSize = DefaultBlockSize) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CheckAliveThrowIfNot() - { + private readonly void CheckAliveThrowIfNot() => UnsafeHelpers.CheckAliveThrowIfNot(_arena, _generation, nameof(ArenaBlockList)); - } private static ArenaBlock* CreateBlock(ArenaAllocator arena, nuint capacity) { @@ -129,7 +127,7 @@ private void CheckAliveThrowIfNot() /// /// Gets the number of elements stored across all blocks. /// - public nuint Count + public readonly nuint Count { get { @@ -141,7 +139,7 @@ public nuint Count /// /// Gets the total allocated capacity across all blocks. /// - public nuint Capacity + public readonly nuint Capacity { get { @@ -192,19 +190,19 @@ public void Add(in T value) /// /// An enumerator over the list. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Enumerator GetEnumerator() + public readonly Enumerator GetEnumerator() { CheckAliveThrowIfNot(); return new Enumerator(_head, _arena); } - IEnumerator IEnumerable.GetEnumerator() + readonly IEnumerator IEnumerable.GetEnumerator() { CheckAliveThrowIfNot(); return new Enumerator(_head, _arena); } - IEnumerator IEnumerable.GetEnumerator() + readonly IEnumerator IEnumerable.GetEnumerator() { CheckAliveThrowIfNot(); return new Enumerator(_head, _arena); @@ -235,7 +233,7 @@ internal Enumerator(ArenaBlock* head, ArenaAllocator arena) /// /// Gets the element in the collection at the current position of the enumerator. /// - public T Current => _current; + public readonly T Current => _current; /// /// Gets the element in the collection at the current position of the enumerator. @@ -299,7 +297,7 @@ public void Dispose() /// Copies the list contents into a contiguous span allocated from the arena. /// /// A span that owns a contiguous copy of the stored values. - public ReadOnlySpan GetSpan() + public readonly ReadOnlySpan GetSpan() { CheckAliveThrowIfNot(); var total = Count; diff --git a/src/SharpArena/Collections/ArenaDictionary.cs b/src/SharpArena/Collections/ArenaDictionary.cs index 4d914ca..9c20694 100644 --- a/src/SharpArena/Collections/ArenaDictionary.cs +++ b/src/SharpArena/Collections/ArenaDictionary.cs @@ -75,7 +75,7 @@ private void AllocateTable(int capacity) private readonly void CheckAlive() => UnsafeHelpers.CheckAliveThrowIfNot(_arena, _generation, nameof(ArenaDictionary<,>)); - private int FindBucket(TKey key) + private readonly int FindBucket(TKey key) { uint capacity = (uint)_header->Capacity; int* buckets = _header->Buckets; @@ -110,7 +110,7 @@ public int Count /// /// Gets a value indicating whether the is read-only. /// - public bool IsReadOnly => false; + public readonly bool IsReadOnly => false; /// /// Gets or sets the element with the specified key. @@ -120,7 +120,7 @@ public int Count /// The property is retrieved and is not found. public TValue this[TKey key] { - get => TryGetValue(key, out var value) ? + readonly get => TryGetValue(key, out var value) ? value : throw new KeyNotFoundException(); set => AddOrUpdate(key, value); } @@ -236,7 +236,7 @@ private void Grow() /// /// The key to locate in the . /// if the contains an element with the key; otherwise, . - public bool ContainsKey(TKey key) + public readonly bool ContainsKey(TKey key) { CheckAlive(); int bucketIdx = FindBucket(key); @@ -249,7 +249,7 @@ public bool ContainsKey(TKey key) /// /// The key to locate in the . /// if the contains an element with the key; otherwise, . - public bool ContainsKey(ReadOnlySpan key) + public readonly bool ContainsKey(ReadOnlySpan key) { if (typeof(TKey) == typeof(ArenaUtf16String)) { @@ -310,7 +310,7 @@ public bool ContainsKey(ReadOnlySpan key) /// /// The key to locate in the . /// if the contains an element with the key; otherwise, . - public bool ContainsKey(ReadOnlySpan key) + public readonly bool ContainsKey(ReadOnlySpan key) { if (typeof(TKey) == typeof(ArenaUtf8String)) { @@ -371,7 +371,7 @@ public bool ContainsKey(ReadOnlySpan key) /// The key whose value to get. /// When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the parameter. This parameter is passed uninitialized. /// if the contains an element with the specified key; otherwise, . - public bool TryGetValue(TKey key, out TValue value) + public readonly bool TryGetValue(TKey key, out TValue value) { CheckAlive(); int bucketIdx = FindBucket(key); @@ -465,7 +465,7 @@ public bool TryGetValue(ReadOnlySpan key, out TValue value) /// The key whose value to get. /// When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the parameter. This parameter is passed uninitialized. /// if the contains an element with the specified key; otherwise, . - public bool TryGetValue(ReadOnlySpan key, out TValue value) + public readonly bool TryGetValue(ReadOnlySpan key, out TValue value) { if (typeof(TKey) == typeof(ArenaUtf8String)) { @@ -590,7 +590,7 @@ public void TrimExcess() /// public bool Remove(TKey key) => throw new NotSupportedException(); void ICollection>.Add(KeyValuePair item) => Add(item.Key, item.Value); - bool ICollection>.Contains(KeyValuePair item) + readonly bool ICollection>.Contains(KeyValuePair item) { if (TryGetValue(item.Key, out var value)) { @@ -618,8 +618,8 @@ void ICollection>.CopyTo(KeyValuePair[] /// Returns an enumerator that iterates through the . /// /// A for the . - public IEnumerator> GetEnumerator() => new Enumerator(this); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public readonly IEnumerator> GetEnumerator() => new Enumerator(this); + readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// /// Enumerates the elements of an . @@ -657,12 +657,12 @@ public bool MoveNext() /// /// Gets the element at the current position of the enumerator. /// - public KeyValuePair Current => _current; + public readonly KeyValuePair Current => _current; /// /// Gets the element at the current position of the enumerator. /// - object IEnumerator.Current => _current; + readonly object IEnumerator.Current => _current; /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. @@ -679,11 +679,11 @@ private struct KeyCollection : ICollection { private readonly ArenaDictionary _dict; public KeyCollection(ArenaDictionary dict) => _dict = dict; - public int Count => _dict.Count; - public bool IsReadOnly => true; + public readonly int Count => _dict.Count; + public readonly bool IsReadOnly => true; public void Add(TKey item) => throw new NotSupportedException(); public void Clear() => throw new NotSupportedException(); - public bool Contains(TKey item) => _dict.ContainsKey(item); + public readonly bool Contains(TKey item) => _dict.ContainsKey(item); public void CopyTo(TKey[] array, int arrayIndex) { int count = _dict.Count; @@ -694,10 +694,10 @@ public void CopyTo(TKey[] array, int arrayIndex) } } public bool Remove(TKey item) => throw new NotSupportedException(); - public IEnumerator GetEnumerator() => new KeyEnumerator(_dict); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public readonly IEnumerator GetEnumerator() => new KeyEnumerator(_dict); + readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - private unsafe struct KeyEnumerator : IEnumerator + private struct KeyEnumerator : IEnumerator { private readonly ArenaDictionary _dict; private int _index; @@ -723,8 +723,8 @@ public bool MoveNext() return false; } - public TKey Current => _current; - object IEnumerator.Current => _current; + public readonly TKey Current => _current; + readonly object IEnumerator.Current => _current; public void Dispose() { } public void Reset() => _index = -1; } diff --git a/src/SharpArena/Collections/ArenaList.cs b/src/SharpArena/Collections/ArenaList.cs index 766e4cf..ea08a3e 100644 --- a/src/SharpArena/Collections/ArenaList.cs +++ b/src/SharpArena/Collections/ArenaList.cs @@ -75,15 +75,13 @@ public ArenaList(ArenaAllocator arena, int initialCapacity = 16) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CheckAliveThrowIfNot() - { + private readonly void CheckAliveThrowIfNot() => UnsafeHelpers.CheckAliveThrowIfNot(_arena, _generation, nameof(ArenaList)); - } /// /// Gets the number of elements stored in the list. /// - public int Length + public readonly int Length { get { @@ -121,7 +119,7 @@ public bool IsEmpty /// /// The zero-based index of the item to access. /// A reference to the item at the requested index. - public ref T this[int index] + public readonly ref T this[int index] { get { @@ -258,7 +256,7 @@ public void Reset() /// /// Gets a pointer to the raw unmanaged data of the list. /// - public T* AsPtr + public readonly T* AsPtr { get { @@ -270,13 +268,13 @@ public T* AsPtr /// /// Gets a writable span over the list's contents. /// - public Span Span => AsSpan(); + public readonly Span Span => AsSpan(); /// /// Provides a writable span view of the stored elements. /// /// A span referencing the list contents. - public Span AsSpan() + public readonly Span AsSpan() { CheckAliveThrowIfNot(); return new Span((T*)_header->Data, _header->Count); @@ -286,7 +284,7 @@ public Span AsSpan() /// Provides a read-only span view of the stored elements. /// /// A read-only span referencing the list contents. - public ReadOnlySpan AsReadOnlySpan() + public readonly ReadOnlySpan AsReadOnlySpan() { CheckAliveThrowIfNot(); return new ReadOnlySpan((T*)_header->Data, _header->Count); @@ -297,7 +295,7 @@ public ReadOnlySpan AsReadOnlySpan() /// /// The object to locate in the list. /// if is found; otherwise, . - public bool Contains(in T item) + public readonly bool Contains(in T item) { CheckAliveThrowIfNot(); var span = AsSpan(); diff --git a/src/SharpArena/Collections/ArenaSet.cs b/src/SharpArena/Collections/ArenaSet.cs index 2a297b6..c6093e2 100644 --- a/src/SharpArena/Collections/ArenaSet.cs +++ b/src/SharpArena/Collections/ArenaSet.cs @@ -70,7 +70,7 @@ private void AllocateTable(int capacity) private readonly void CheckAlive() => UnsafeHelpers.CheckAliveThrowIfNot(_arena, _generation, nameof(ArenaSet<>)); - private int FindBucket(T item) + private readonly int FindBucket(T item) { var capacity = _header->Capacity; var buckets = _header->Buckets; @@ -82,9 +82,15 @@ private int FindBucket(T item) while (true) { var entryIdxPlusOne = buckets[index]; - if (entryIdxPlusOne == 0) return (int)index; + if (entryIdxPlusOne == 0) + { + return (int)index; + } - if (entries[entryIdxPlusOne - 1].Equals(item)) return (int)index; + if (entries[entryIdxPlusOne - 1].Equals(item)) + { + return (int)index; + } index = (index + 1) & mask; } @@ -93,7 +99,7 @@ private int FindBucket(T item) /// /// Gets the number of elements contained in the . /// - public int Count + public readonly int Count { get { @@ -105,7 +111,7 @@ public int Count /// /// Gets a value indicating whether the is read-only. /// - public bool IsReadOnly => false; + public readonly bool IsReadOnly => false; /// /// Adds an element to the current set and returns a value to indicate if the element was successfully added. @@ -124,7 +130,10 @@ public bool Add(T item) var bucketIdx = FindBucket(item); var entryIdxPlusOne = _header->Buckets[bucketIdx]; - if (entryIdxPlusOne != 0) return false; + if (entryIdxPlusOne != 0) + { + return false; + } var newEntryIdx = _header->Count++; _header->Buckets[bucketIdx] = newEntryIdx + 1; @@ -180,7 +189,7 @@ public void Clear() /// /// The object to locate in the . /// if is found in the ; otherwise, . - public bool Contains(T item) + public readonly bool Contains(T item) { CheckAlive(); var bucketIdx = FindBucket(item); @@ -192,9 +201,13 @@ public bool Contains(T item) /// /// The object to locate in the . /// if is found in the ; otherwise, . - public bool Contains(ReadOnlySpan item) + public readonly bool Contains(ReadOnlySpan item) { - if (typeof(T) != typeof(ArenaUtf16String)) return false; + if (typeof(T) != typeof(ArenaUtf16String)) + { + return false; + } + CheckAlive(); var capacity = _header->Capacity; @@ -207,9 +220,15 @@ public bool Contains(ReadOnlySpan item) while (true) { var entryIdxPlusOne = buckets[index]; - if (entryIdxPlusOne == 0) return false; + if (entryIdxPlusOne == 0) + { + return false; + } - if (entries[entryIdxPlusOne - 1].Equals(item)) return true; + if (entries[entryIdxPlusOne - 1].Equals(item)) + { + return true; + } index = (index + 1) & mask; } @@ -220,9 +239,13 @@ public bool Contains(ReadOnlySpan item) /// /// The object to locate in the . /// if is found in the ; otherwise, . - public bool Contains(ReadOnlySpan item) + public readonly bool Contains(ReadOnlySpan item) { - if (typeof(T) != typeof(ArenaUtf8String)) return false; + if (typeof(T) != typeof(ArenaUtf8String)) + { + return false; + } + CheckAlive(); var capacity = _header->Capacity; @@ -235,9 +258,15 @@ public bool Contains(ReadOnlySpan item) while (true) { var entryIdxPlusOne = buckets[index]; - if (entryIdxPlusOne == 0) return false; + if (entryIdxPlusOne == 0) + { + return false; + } - if (entries[entryIdxPlusOne - 1].Equals(item)) return true; + if (entries[entryIdxPlusOne - 1].Equals(item)) + { + return true; + } index = (index + 1) & mask; } @@ -253,9 +282,20 @@ public bool Contains(ReadOnlySpan item) /// The number of elements in the source is greater than the available space from to the end of the destination . public void CopyTo(T[] array, int arrayIndex) { - if (array == null) throw new ArgumentNullException(nameof(array)); - if (arrayIndex < 0 || arrayIndex > array.Length) throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - if (array.Length - arrayIndex < Count) throw new ArgumentException("Destination array is too small."); + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + if (arrayIndex < 0 || arrayIndex > array.Length) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + } + + if (array.Length - arrayIndex < Count) + { + throw new ArgumentException("Destination array is too small."); + } CheckAlive(); var count = _header->Count; @@ -284,7 +324,10 @@ public void TrimExcess() var newCap = 1; while (newCap < count / LoadFactor) newCap <<= 1; - if (newCap >= _header->Capacity) return; + if (newCap >= _header->Capacity) + { + return; + } var oldEntries = (T*)_header->Entries; var newBuckets = (int*)_arena.Alloc((nuint)newCap * sizeof(int), align: 16); @@ -317,13 +360,13 @@ public void TrimExcess() /// Returns an enumerator that iterates through the . /// /// A for the . - public IEnumerator GetEnumerator() => new Enumerator(this); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public readonly IEnumerator GetEnumerator() => new Enumerator(this); + readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// /// Enumerates the elements of an . /// - public unsafe struct Enumerator : IEnumerator + private struct Enumerator : IEnumerator { private readonly ArenaSet _set; private int _index; @@ -344,7 +387,10 @@ public bool MoveNext() { _set.CheckAlive(); var header = _set._header; - if (header == null) return false; + if (header == null) + { + return false; + } if (++_index < header->Count) { @@ -357,12 +403,12 @@ public bool MoveNext() /// /// Gets the element in the collection at the current position of the enumerator. /// - public T Current => _current; + public readonly T Current => _current; /// /// Gets the element in the collection at the current position of the enumerator. /// - object IEnumerator.Current => _current; + readonly object IEnumerator.Current => _current; /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. @@ -384,15 +430,23 @@ public void Dispose() { } /// /// Determines whether the current set is a proper (strict) subset of a specified collection. /// - public bool IsProperSubsetOf(IEnumerable other) + public readonly bool IsProperSubsetOf(IEnumerable other) { - if (other == null) throw new ArgumentNullException(nameof(other)); + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + CheckAlive(); var matchCount = 0; var otherSet = new HashSet(other); foreach (var item in this) { - if (!otherSet.Contains(item)) return false; + if (!otherSet.Contains(item)) + { + return false; + } + matchCount++; } return otherSet.Count > matchCount; @@ -401,14 +455,22 @@ public bool IsProperSubsetOf(IEnumerable other) /// /// Determines whether the current set is a proper (strict) superset of a specified collection. /// - public bool IsProperSupersetOf(IEnumerable other) + public readonly bool IsProperSupersetOf(IEnumerable other) { - if (other == null) throw new ArgumentNullException(nameof(other)); + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + CheckAlive(); var otherCount = 0; foreach (var item in other) { - if (!Contains(item)) return false; + if (!Contains(item)) + { + return false; + } + otherCount++; } return Count > otherCount; @@ -417,15 +479,26 @@ public bool IsProperSupersetOf(IEnumerable other) /// /// Determines whether a set is a subset of a specified collection. /// - public bool IsSubsetOf(IEnumerable other) + public readonly bool IsSubsetOf(IEnumerable other) { - if (other == null) throw new ArgumentNullException(nameof(other)); + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + CheckAlive(); - if (Count == 0) return true; + if (Count == 0) + { + return true; + } + var otherSet = new HashSet(other); foreach (var item in this) { - if (!otherSet.Contains(item)) return false; + if (!otherSet.Contains(item)) + { + return false; + } } return true; } @@ -433,13 +506,20 @@ public bool IsSubsetOf(IEnumerable other) /// /// Determines whether the current set is a superset of a specified collection. /// - public bool IsSupersetOf(IEnumerable other) + public readonly bool IsSupersetOf(IEnumerable other) { - if (other == null) throw new ArgumentNullException(nameof(other)); + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + CheckAlive(); foreach (var item in other) { - if (!Contains(item)) return false; + if (!Contains(item)) + { + return false; + } } return true; } @@ -447,13 +527,20 @@ public bool IsSupersetOf(IEnumerable other) /// /// Determines whether the current set overlaps with the specified collection. /// - public bool Overlaps(IEnumerable other) + public readonly bool Overlaps(IEnumerable other) { - if (other == null) throw new ArgumentNullException(nameof(other)); + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + CheckAlive(); foreach (var item in other) { - if (Contains(item)) return true; + if (Contains(item)) + { + return true; + } } return false; } @@ -461,14 +548,22 @@ public bool Overlaps(IEnumerable other) /// /// Determines whether the current set and the specified collection contain the same elements. /// - public bool SetEquals(IEnumerable other) + public readonly bool SetEquals(IEnumerable other) { - if (other == null) throw new ArgumentNullException(nameof(other)); + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + CheckAlive(); var otherCount = 0; foreach (var item in other) { - if (!Contains(item)) return false; + if (!Contains(item)) + { + return false; + } + otherCount++; } return Count == otherCount; @@ -482,7 +577,11 @@ public bool SetEquals(IEnumerable other) /// public void UnionWith(IEnumerable other) { - if (other == null) throw new ArgumentNullException(nameof(other)); + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + CheckAlive(); foreach (var item in other) Add(item); } diff --git a/src/SharpArena/Collections/Hashing.cs b/src/SharpArena/Collections/Hashing.cs index 15cf4b7..7dffd8e 100644 --- a/src/SharpArena/Collections/Hashing.cs +++ b/src/SharpArena/Collections/Hashing.cs @@ -22,14 +22,10 @@ public static uint Hash(T value) where T : unmanaged } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint HashString(ReadOnlySpan value) - { - return (uint)XxHash3.HashToUInt64(MemoryMarshal.AsBytes(value)); - } + public static uint HashString(ReadOnlySpan value) => + (uint)XxHash3.HashToUInt64(MemoryMarshal.AsBytes(value)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint HashUtf8(ReadOnlySpan value) - { - return (uint)XxHash3.HashToUInt64(value); - } + public static uint HashUtf8(ReadOnlySpan value) => + (uint)XxHash3.HashToUInt64(value); }