Skip to content

Commit 9eba1a0

Browse files
author
LoneWandererProductions
committed
add padding
1 parent d6fe1c9 commit 9eba1a0

3 files changed

Lines changed: 34 additions & 33 deletions

File tree

MemoryManager.Core/MemoryCanary.cs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* PROGRAMMER: Peter Geinitz (Wayfarer)
77
*/
88

9+
using System;
910
using System.Runtime.CompilerServices;
1011

1112
namespace MemoryManager.Core
@@ -14,9 +15,15 @@ namespace MemoryManager.Core
1415
/// Canary class provides a simple mechanism for detecting buffer overruns and underruns in debug builds by placing known "canary" values before and after user allocations.
1516
/// In release builds, the canary logic is completely stripped out to ensure zero overhead,
1617
/// making it an effective tool for catching memory corruption issues during development without impacting performance in production.
18+
/// It additionally acts as an alignment boundary engine, snapping unmanaged pointers cleanly to hardware memory channels.
1719
/// </summary>
1820
public static class MemoryCanary
1921
{
22+
/// <summary>
23+
/// The byte alignment requirement boundary (Must be a power of two, e.g., 16, 32, 64)
24+
/// </summary>
25+
private const int Alignment = 16;
26+
2027
/// <summary>
2128
/// The size
2229
/// </summary>
@@ -27,49 +34,52 @@ public static class MemoryCanary
2734
/// </summary>
2835
private const uint Magic = 0xDEADBEEF;
2936

37+
/// <summary>
38+
/// Pre-calculated front window padding size required to fit the pre-canary and maintain power-of-two alignment
39+
/// </summary>
40+
#if DEBUG
41+
private static readonly int FrontPadding = (Size + (Alignment - 1)) & ~(Alignment - 1);
42+
#else
43+
private static readonly int FrontPadding = 0;
44+
#endif
45+
3046
/// <summary>
3147
/// Calculates the total physical footprint needed in the allocator block pool.
3248
/// </summary>
3349
/// <param name="userSize">Size of the user.</param>
34-
/// <returns></returns>
50+
/// <returns>Total block allocation size required, rounded up to the nearest alignment multiple.</returns>
3551
[MethodImpl(MethodImplOptions.AggressiveInlining)]
3652
public static int GetPhysicalSize(int userSize)
3753
{
3854
#if DEBUG
39-
return userSize + Size * 2;
55+
// Raw physical size needs space for front tracking layout padding, user data payload, and post-canary trail bytes
56+
int rawSize = FrontPadding + userSize + Size;
57+
return (rawSize + (Alignment - 1)) & ~(Alignment - 1);
4058
#else
41-
return userSize;
59+
return (userSize + (Alignment - 1)) & ~(Alignment - 1);
4260
#endif
4361
}
4462

4563
/// <summary>
4664
/// Maps a raw physical block offset to a user-facing offset past the pre-canary.
4765
/// </summary>
4866
/// <param name="physicalOffset">The physical offset.</param>
49-
/// <returns></returns>
67+
/// <returns>Aligned offset position where user data begins.</returns>
5068
[MethodImpl(MethodImplOptions.AggressiveInlining)]
5169
public static int GetUserOffset(int physicalOffset)
5270
{
53-
#if DEBUG
54-
return physicalOffset + Size;
55-
#else
56-
return physicalOffset;
57-
#endif
71+
return physicalOffset + FrontPadding;
5872
}
5973

6074
/// <summary>
6175
/// Maps a user data offset back to the absolute physical block start coordinate.
6276
/// </summary>
6377
/// <param name="userOffset">The user offset.</param>
64-
/// <returns></returns>
78+
/// <returns>Absolute start address index of the complete tracking memory block pool chunk.</returns>
6579
[MethodImpl(MethodImplOptions.AggressiveInlining)]
6680
public static int GetPhysicalOffset(int userOffset)
6781
{
68-
#if DEBUG
69-
return userOffset - Size;
70-
#else
71-
return userOffset;
72-
#endif
82+
return userOffset - FrontPadding;
7383
}
7484

7585
/// <summary>
@@ -97,7 +107,7 @@ public static unsafe void WriteGuardBands(nint buffer, int userOffset, int userS
97107
/// <param name="userSize">Size of the user.</param>
98108
/// <param name="handleId">The handle identifier.</param>
99109
/// <exception cref="AccessViolationException">CRITICAL HEAP CORRUPTION DETECTED: Buffer boundary breach on Handle ID {handleId}. " +
100-
/// $"Expected Canary: 0x{Magic:X}, Pre-Site: 0x{preCanary:X}, Post-Site: 0x{postCanary:X}</exception>
110+
/// $"Expected Canary: 0x{Magic:X}, Pre-Site: 0x{preCanary:X}, Post-Site: 0x{postCanary:X}</exception>
101111
[MethodImpl(MethodImplOptions.AggressiveInlining)]
102112
public static unsafe void Validate(nint buffer, int userOffset, int userSize, int handleId)
103113
{

MemoryManager.Tests/BlobManagerTests.cs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ namespace MemoryManager.Tests
1616
[TestClass]
1717
public class BlobManagerTests
1818
{
19-
/// <summary>
20-
/// BLOBs the manager allocate free and compact works correctly.
21-
/// </summary>
2219
/// <summary>
2320
/// BLOBs the manager allocate free and compact works correctly.
2421
/// </summary>
@@ -38,10 +35,9 @@ public void BlobManager_AllocateFreeAndCompact_WorksCorrectly()
3835
var h2 = blobManager.Allocate(100);
3936
var h3 = blobManager.Allocate(100);
4037

41-
// Dynamically detect canary size from the first block's user offset shift
42-
int canarySize = blobManager.GetEntry(h1).Offset; // 4 bytes in DEBUG, 0 bytes in RELEASE
43-
int totalCanaryOverhead = 3 * (canarySize * 2); // 3 blocks * 2 canaries each
44-
int expectedFreeSpace = capacity - (300 + totalCanaryOverhead);
38+
// Query the core alignment engine directly to determine the true physical footprint
39+
int physicalBlockSize = MemoryCanary.GetPhysicalSize(100);
40+
int expectedFreeSpace = capacity - (3 * physicalBlockSize);
4541

4642
Assert.AreEqual(expectedFreeSpace, blobManager.FreeSpace(), $"Should have exactly {expectedFreeSpace} bytes free.");
4743

@@ -83,7 +79,11 @@ public void Performance_FastLane_Vs_LinearLane_Showdown()
8379
{
8480
const int count = 50000;
8581
const int size = 32; // 32-byte structs (e.g., matrices, vectors)
86-
const int capacity = count * size * 2; // Plenty of room
82+
83+
// Calculate capacity using the true aligned physical size,
84+
// and add a small 4KB cushion padding to accommodate non-reclaiming warmup bumps
85+
int physicalSize = MemoryCanary.GetPhysicalSize(size);
86+
int capacity = (count * physicalSize) + 4096;
8787

8888
// Both lanes require a SlowLane reference in their constructors
8989
using var slowLane = new SlowLane(capacity);
@@ -142,9 +142,6 @@ public void Performance_FastLane_Vs_LinearLane_Showdown()
142142
Trace.WriteLine("===== DEALLOCATION SPEED (50,000 objects) =====");
143143
Trace.WriteLine($"LinearLane (Bump) : {linearFreeTicks:N0} ticks");
144144
Trace.WriteLine($"FastLane (List) : {fastFreeTicks:N0} ticks");
145-
146-
// The Bump allocator should absolutely destroy the Free-List in allocation speed
147-
// because it doesn't have to loop through the _freeBlocks array!
148145
}
149146
}
150147
}

README.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,6 @@ To move this system out of the prototyping phase and into a hardened, production
5050
* **Current State:** When compaction triggers, the compactor engine allocates an entirely new unmanaged heap block via `Marshal.AllocHGlobal` and slides all survivors over via memory block copying. For massive heaps (e.g., 512MB+), this causes noticeable block stutters (latency spikes) and temporarily forces a **double memory footprint**.
5151
* **Production Fix:** Implement an **Incremental/Phased Compactor** that only relocates a small chunk of fragmented blocks per frame tick, or enforce strict zero-compaction rules where arenas are completely cleared and cycled out at deterministic boundaries (e.g., scene changes).
5252

53-
### 3. Hardware Memory Alignment Blindness
54-
* **Current State:** The bump allocators currently increment layouts directly by byte size (`_nextFreeOffset += size`). If a user allocates an odd structural layout (e.g., a 7-byte struct), subsequent items are placed on unaligned memory addresses, causing the CPU to fetch multiple cache-lines for single read instructions, tanking cache line efficiency.
55-
* **Production Fix:** Force all lane offsets to snap cleanly to hardware alignment boundaries (e.g., 16-byte, 32-byte, or 64-byte boundaries for SIMD alignment) using power-of-two bitwise masking layout constraints:
56-
```csharp
57-
alignedOffset = (offset + (alignment - 1)) & ~(alignment - 1);
58-
5953
---
6054

6155
## 🧩 Config Presets & Advanced Usage

0 commit comments

Comments
 (0)