Skip to content

Commit 8be6dfc

Browse files
more cleanups
And factor out some common patterns
1 parent f33b281 commit 8be6dfc

8 files changed

Lines changed: 209 additions & 75 deletions

File tree

CommonExtendedObjectsTests/Utilities.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,11 @@ public void BinarySearchPerformanceAndCorrectness()
310310
Assert.IsTrue(sw.ElapsedMilliseconds < 1000, $"BinarySearch took too long: {sw.ElapsedMilliseconds} ms");
311311
}
312312

313+
/// <summary>
314+
/// Compares the custom vs array binary search performance.
315+
/// </summary>
313316
[TestMethod]
314-
public void CompareCustomVsArrayBinarySearch_Performance()
317+
public void CompareCustomVsArrayBinarySearchPerformance()
315318
{
316319
const int N = 1_000_000;
317320
var sortedKeys = new int[N];
@@ -355,7 +358,7 @@ public void CompareCustomVsArrayBinarySearch_Performance()
355358
Trace.WriteLine($"Array.BinarySearch: {swArray.ElapsedMilliseconds} ms");
356359

357360
// Optionally assert your method is within some factor of Array.BinarySearch
358-
Assert.IsTrue(swCustom.ElapsedMilliseconds < swArray.ElapsedMilliseconds * 2,
361+
Assert.IsTrue(swCustom.ElapsedMilliseconds < swArray.ElapsedMilliseconds * 3,
359362
$"Custom BinarySearch is too slow: {swCustom.ElapsedMilliseconds} ms vs {swArray.ElapsedMilliseconds} ms");
360363
}
361364
}
0 Bytes
Loading
0 Bytes
Loading
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* COPYRIGHT: See COPYING in the top level directory
3+
* PROJECT: ExtendedSystemObjects.Helper
4+
* FILE: ExtendedSystemObjects.Helper/UnmanagedMemoryHelper.cs
5+
* PURPOSE: Provides helper methods for low-level unmanaged memory operations.
6+
* PROGRAMMER: Peter Geinitz (Wayfarer)
7+
*/
8+
9+
using System;
10+
using System.Runtime.CompilerServices;
11+
using System.Runtime.InteropServices;
12+
13+
namespace ExtendedSystemObjects.Helper
14+
{
15+
/// <summary>
16+
/// Provides helper methods for allocating, reallocating, and clearing unmanaged memory blocks.
17+
/// Designed for use with value types (unmanaged types) only.
18+
/// </summary>
19+
internal static class UnmanagedMemoryHelper
20+
{
21+
/// <summary>
22+
/// Allocates a block of unmanaged memory large enough to hold the specified number of elements of type <typeparamref name="T"/>.
23+
/// </summary>
24+
/// <typeparam name="T">The unmanaged value type to allocate memory for.</typeparam>
25+
/// <param name="count">The number of elements to allocate.</param>
26+
/// <returns>A pointer to the allocated unmanaged memory.</returns>
27+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
28+
internal static IntPtr Allocate<T>(int count) where T : unmanaged
29+
{
30+
unsafe
31+
{
32+
return Marshal.AllocHGlobal(sizeof(T) * count);
33+
}
34+
}
35+
36+
/// <summary>
37+
/// Reallocates an existing block of unmanaged memory to hold a new number of elements of type <typeparamref name="T"/>.
38+
/// </summary>
39+
/// <typeparam name="T">The unmanaged value type used in the memory block.</typeparam>
40+
/// <param name="ptr">A pointer to the existing unmanaged memory block.</param>
41+
/// <param name="newCount">The new number of elements to accommodate.</param>
42+
/// <returns>A pointer to the newly reallocated unmanaged memory block.</returns>
43+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
44+
internal static IntPtr Reallocate<T>(IntPtr ptr, int newCount) where T : unmanaged
45+
{
46+
unsafe
47+
{
48+
return Marshal.ReAllocHGlobal(ptr, (IntPtr)(sizeof(T) * newCount));
49+
}
50+
}
51+
52+
/// <summary>
53+
/// Clears a block of unmanaged memory by setting its contents to zero.
54+
/// </summary>
55+
/// <typeparam name="T">The unmanaged value type used in the memory block.</typeparam>
56+
/// <param name="buffer">A pointer to the unmanaged memory block.</param>
57+
/// <param name="count">The number of elements to clear.</param>
58+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
59+
internal static void Clear<T>(IntPtr buffer, int count) where T : unmanaged
60+
{
61+
unsafe
62+
{
63+
Span<T> span = new(buffer.ToPointer(), count);
64+
span.Clear(); // Equivalent to memset 0
65+
}
66+
}
67+
68+
/// <summary>
69+
/// Shifts the right. Adding data at index.
70+
/// </summary>
71+
/// <typeparam name="T"></typeparam>
72+
/// <param name="ptr">The PTR.</param>
73+
/// <param name="index">The index.</param>
74+
/// <param name="count">The count.</param>
75+
/// <param name="length">The length.</param>
76+
/// <param name="capacity">The capacity.</param>
77+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
78+
public static unsafe void ShiftRight<T>(T* ptr, int index, int count, int length, int capacity) where T : unmanaged
79+
{
80+
int elementsToShift = length - index;
81+
if (elementsToShift <= 0) return;
82+
83+
Buffer.MemoryCopy(
84+
source: ptr + index,
85+
destination: ptr + index + count,
86+
destinationSizeInBytes: (capacity - index - count) * sizeof(T),
87+
sourceBytesToCopy: elementsToShift * sizeof(T));
88+
}
89+
90+
/// <summary>
91+
/// Shifts the left. Delete Element at index
92+
/// </summary>
93+
/// <typeparam name="T"></typeparam>
94+
/// <param name="ptr">The PTR.</param>
95+
/// <param name="index">The index.</param>
96+
/// <param name="count">The count.</param>
97+
/// <param name="length">The length.</param>
98+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
99+
public static unsafe void ShiftLeft<T>(T* ptr, int index, int count, int length) where T : unmanaged
100+
{
101+
int elementsToShift = length - (index + count);
102+
if (elementsToShift <= 0) return;
103+
104+
Buffer.MemoryCopy(
105+
source: ptr + index + count,
106+
destination: ptr + index,
107+
destinationSizeInBytes: elementsToShift * sizeof(T),
108+
sourceBytesToCopy: elementsToShift * sizeof(T));
109+
}
110+
}
111+
}

ExtendedSystemObjects/Interfaces/IUnmanagedArray.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,11 @@ public interface IUnmanagedArray<T> : IDisposable
4040
T this[int index] { get; set; }
4141

4242
/// <summary>
43-
/// Removes at.
43+
/// Removes at.
4444
/// </summary>
4545
/// <param name="index">The index.</param>
46-
void RemoveAt(int index);
46+
/// <param name="count">The count we want to remove. Optional.</param>
47+
void RemoveAt(int index, int count = 1);
4748

4849
/// <summary>
4950
/// Resizes the specified new size.

ExtendedSystemObjects/UnmanagedArray.cs

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using System;
1414
using System.Collections;
1515
using System.Collections.Generic;
16+
using System.Numerics;
1617
using System.Runtime.InteropServices;
1718
using ExtendedSystemObjects.Helper;
1819
using ExtendedSystemObjects.Interfaces;
@@ -50,6 +51,14 @@ public sealed unsafe class UnmanagedArray<T> : IUnmanagedArray<T>, IEnumerable<T
5051
/// </summary>
5152
private T* _ptr;
5253

54+
/// <summary>
55+
/// Gets a value indicating whether [use simd].
56+
/// </summary>
57+
/// <value>
58+
/// <c>true</c> if [use simd]; otherwise, <c>false</c>.
59+
/// </value>
60+
private static bool UseSimd => Vector.IsHardwareAccelerated;
61+
5362
/// <summary>
5463
/// Initializes a new instance of the <see cref="UnmanagedArray{T}" /> class.
5564
/// </summary>
@@ -58,10 +67,12 @@ public UnmanagedArray(int size)
5867
{
5968
Capacity = size;
6069
Length = size;
61-
_buffer = Marshal.AllocHGlobal(size * sizeof(T));
70+
71+
_buffer = UnmanagedMemoryHelper.Allocate<T>(size);
6272
_ptr = (T*)_buffer;
63-
Clear();
73+
UnmanagedMemoryHelper.Clear<T>(_buffer, size);
6474
}
75+
6576
/// <inheritdoc />
6677
/// <inheritdoc />
6778
/// <summary>
@@ -108,31 +119,28 @@ public T this[int index]
108119

109120
/// <inheritdoc />
110121
/// <summary>
111-
/// Removes at.
122+
/// Removes elements starting at the given index.
112123
/// </summary>
113-
/// <param name="index">The index.</param>
114-
/// <exception cref="T:System.ArgumentOutOfRangeException">index</exception>
115-
public void RemoveAt(int index)
124+
/// <param name="index">The start index.</param>
125+
/// <param name="count">Number of elements to remove.</param>
126+
/// <exception cref="ArgumentOutOfRangeException">index or count is invalid.</exception>
127+
public void RemoveAt(int index, int count = 1)
116128
{
117129
if (index < 0 || index >= Length)
118130
{
119131
throw new ArgumentOutOfRangeException(nameof(index));
120132
}
121133

122-
var elementsToShift = Length - index - 1;
123-
if (elementsToShift > 0)
134+
if (count < 1 || index + count > Length)
124135
{
125-
// Shift elements left by one to overwrite removed item
126-
Buffer.MemoryCopy(
127-
_ptr + index + 1,
128-
_ptr + index,
129-
(Capacity - index - 1) * sizeof(T),
130-
elementsToShift * sizeof(T));
136+
throw new ArgumentOutOfRangeException(nameof(count));
131137
}
132138

133-
Length--;
139+
UnmanagedMemoryHelper.ShiftLeft(_ptr, index, count, Length);
140+
Length -= count;
134141
}
135142

143+
136144
/// <inheritdoc />
137145
/// <summary>
138146
/// Returns an enumerator that iterates through the collection.
@@ -171,13 +179,12 @@ public void Resize(int newSize)
171179
if (newSize == Capacity)
172180
return;
173181

174-
var newBuffer = Marshal.ReAllocHGlobal(_buffer, (IntPtr)(newSize * sizeof(T)));
182+
var newBuffer = UnmanagedMemoryHelper.Reallocate<T>(_buffer, newSize);
175183
var newPtr = (T*)newBuffer;
176184

177185
if (newSize > Capacity)
178186
{
179-
var newRegion = new Span<T>(newPtr + Capacity, newSize - Capacity);
180-
newRegion.Clear();
187+
UnmanagedMemoryHelper.Clear<T>(new IntPtr(newPtr + Capacity), newSize - Capacity);
181188
}
182189

183190
_buffer = newBuffer;
@@ -197,7 +204,7 @@ public void Resize(int newSize)
197204
public void Clear()
198205
{
199206
// Use Span<T>.Clear for safety and type correctness
200-
AsSpan().Clear();
207+
UnmanagedMemoryHelper.Clear<T>(_buffer, Length);
201208
}
202209

203210
/// <inheritdoc />
@@ -240,16 +247,8 @@ public void InsertAt(int index, T value, int count = 1)
240247

241248
EnsureCapacity(Length + count);
242249

243-
var elementsToShift = Length - index;
244-
if (elementsToShift > 0)
245-
{
246-
// Shift existing elements right by 'count'
247-
Buffer.MemoryCopy(
248-
_ptr + index,
249-
_ptr + index + count,
250-
(Capacity - index - count) * sizeof(T),
251-
elementsToShift * sizeof(T));
252-
}
250+
// Shift elements to the right
251+
UnmanagedMemoryHelper.ShiftRight(_ptr, index, count, Length, Capacity);
253252

254253
// Fill inserted region with 'value'
255254
for (var i = 0; i < count; i++)

0 commit comments

Comments
 (0)