hlsf error is solved. HLSF is now full functional available.

This commit is contained in:
Youmu Liu
2025-12-02 18:39:41 +08:00
parent 482cddd6bf
commit dc39baeac6
5 changed files with 114 additions and 281 deletions
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Root Type="DevExpress.CodeRush.VisualizePlugins.StructuralHighlighting.Options.StructuralHighlightingOptions">
<Options Language="Neutral">
<Enabled>False</Enabled>
</Options>
</Root>
+1 -19
View File
@@ -114,22 +114,4 @@ dotnet_naming_style.以_i_开始.word_separator =
dotnet_naming_style.以_i_开始.capitalization = pascal_case dotnet_naming_style.以_i_开始.capitalization = pascal_case
dotnet_naming_style.帕斯卡拼写法.required_prefix = dotnet_naming_style.帕斯卡拼写法.required_prefix =
dotnet_naming_style.帕斯卡拼写法.required_suffix = dotenv
dotnet_naming_style.帕斯卡拼写法.word_separator =
dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case
dotnet_naming_style.帕斯卡拼写法.required_prefix =
dotnet_naming_style.帕斯卡拼写法.required_suffix =
dotnet_naming_style.帕斯卡拼写法.word_separator =
dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case
[*.{cs,vb}]
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_property = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_event = false:silent
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
end_of_line = crlf
tab_width = 4
indent_size = 4
dotnet_style_operator_placement_when_wrapping = beginning_of_line
@@ -60,9 +60,12 @@ namespace StgSharp.HighPerformance.Memory
s = 0; s = 0;
i++; i++;
} }
if (i >= levelSizeArray.Length) if (i > MaxLevel)
{ {
if (TryAllocateNew16MBBlock(out handle)) if (TryPopLevelForAllocation(10, 1, out handle))
{
break;
} else if (TryAllocateNew32MBBlock(out handle))
{ {
break; break;
} }
@@ -106,7 +109,7 @@ namespace StgSharp.HighPerformance.Memory
long offset = (long)e->Position - sizeToSlice - (long)m_Buffer; // offset of buffer to slice long offset = (long)e->Position - sizeToSlice - (long)m_Buffer; // offset of buffer to slice
long end = (long)e->Position - (long)m_Buffer; // end offset of buffer to slice long end = (long)e->Position - (long)m_Buffer; // end offset of buffer to slice
int i = (BitOperations.TrailingZeroCount(remain) - 6) / 2; int i = (BitOperations.TrailingZeroCount(offset) - 6) / 2;
i = int.Min(10, i); i = int.Min(10, i);
for (; i < levelSizeArray.Length; i++) for (; i < levelSizeArray.Length; i++)
{ {
@@ -116,7 +119,7 @@ namespace StgSharp.HighPerformance.Memory
int count = (int)((long.Min(edge, end) - offset) / size); int count = (int)((long.Min(edge, end) - offset) / size);
if (count > 0) if (count > 0)
{ {
int newSize = count * size; long newSize = count * size;
Entry* newEntry = (Entry*)_entries.Allocate(); Entry* newEntry = (Entry*)_entries.Allocate();
InsertEntryBefore(newEntry, next); InsertEntryBefore(newEntry, next);
@@ -142,7 +145,7 @@ namespace StgSharp.HighPerformance.Memory
int count = (int)((long.Min(edge, end) - offset) / upper); int count = (int)((long.Min(edge, end) - offset) / upper);
if (count > 0) if (count > 0)
{ {
int newSize = count * upper; long newSize = (long)count * (long)upper;
Entry* newEntry = (Entry*)_entries.Allocate(); Entry* newEntry = (Entry*)_entries.Allocate();
InsertEntryBefore(newEntry, next); InsertEntryBefore(newEntry, next);
@@ -153,7 +156,7 @@ namespace StgSharp.HighPerformance.Memory
b->EntryRef = newEntry; b->EntryRef = newEntry;
PushBucketToLevel(i, count - 1, b); PushBucketToLevel(i, count - 1, b);
offset += newSize; offset += newSize;
remain -= (long)newSize; remain -= newSize;
if (remain <= 0) { if (remain <= 0) {
return; return;
} }
@@ -167,7 +170,7 @@ namespace StgSharp.HighPerformance.Memory
int count = (int)((long.Min(edge, end) - offset) / size); int count = (int)((long.Min(edge, end) - offset) / size);
if (count > 0) if (count > 0)
{ {
int newSize = count * size; long newSize = count * size;
Entry* newEntry = (Entry*)_entries.Allocate(); Entry* newEntry = (Entry*)_entries.Allocate();
InsertEntryBefore(newEntry, next); InsertEntryBefore(newEntry, next);
@@ -180,7 +183,7 @@ namespace StgSharp.HighPerformance.Memory
b->PreviousLevel = null; b->PreviousLevel = null;
PushBucketToLevel(i, count - 1, b); PushBucketToLevel(i, count - 1, b);
offset += newSize; offset += newSize;
remain -= (long)newSize; remain -= newSize;
} }
if (remain <= 0) { if (remain <= 0) {
return; return;
@@ -197,7 +200,7 @@ namespace StgSharp.HighPerformance.Memory
/// <returns> /// <returns>
/// True if allocation succeeded /// True if allocation succeeded
/// </returns> /// </returns>
private bool TryAllocateNew16MBBlock(out Entry* handle) private bool TryAllocateNew32MBBlock(out Entry* handle)
{ {
nuint pos = _spareMemory->Position; nuint pos = _spareMemory->Position;
long remain = _spareMemory->Size; long remain = _spareMemory->Size;
@@ -206,7 +209,7 @@ namespace StgSharp.HighPerformance.Memory
handle = null; handle = null;
return false; return false;
} }
long size = remain > 16 * 1024 * 1024L ? 16 * 1024 * 1024L : remain; long size = remain > 32 * 1024 * 1024L ? 32 * 1024 * 1024L : remain;
// build new entry // build new entry
@@ -1,271 +1,109 @@
# HLSF (Hybrid Layer Segregated Fit) Allocator # HLSF (Hybrid Layer Segregated Fit) Allocator
A high-performance, O(1) memory allocator designed for real-time applications that demand predictable allocation and deallocation performance. A constant-time (O(1)) allocator for real-time, high-throughput scenarios. It uses layered size classes with segmented buckets and stores all metadata externally for clean user memory and better cache locality.
## Overview ## Key Points
- O(1) allocate/free with 10 levels: 64B ★ 16MB; each level split into 1~/2~/3~ segments
The Hybrid Layer Segregated Fit (HLSF) allocator is a sophisticated memory management system that combines the benefits of segregated free lists with layered size classification. It provides constant-time allocation and deallocation operations while maintaining excellent memory utilization and low fragmentation. - External metadata (`Entry`, `BucketNode`) in dedicated SLABs; user blocks have no headers
- Configurable alignment (-16B); large block cap per allocation: 32MB
## Key Features - Single-threaded implementation (external sync required for multi-threaded use)
### Performance Characteristics
- **O(1) Time Complexity**: Both allocation and deallocation operations execute in constant time
- **High Throughput**: Capable of handling millions of operations per second
- **Low Latency**: Predictable performance suitable for real-time applications
- **Excellent Deallocation Speed**: Superior free performance compared to traditional allocators
### Memory Management
- **External Metadata Storage**: All allocation metadata is stored in external SLAB allocators, keeping the main allocation area clean
- **Native Byte Alignment Control**: Configurable alignment (minimum 16 bytes) with hardware-optimized defaults
- **Large Block Support**: Single allocation limit of 32MB per request
- **Efficient Memory Utilization**: Low overhead with intelligent block merging
### Architecture
- **10-Level Hierarchy**: Size classes from 64 bytes to 16MB (64, 256, 1KB, 4KB, 16KB, 64KB, 256KB, 1MB, 4MB, 16MB)
- **3-Segment Segregation**: Each level is divided into 3 segments for better size distribution
- **Hybrid Approach**: Combines best aspects of segregated fit and layered allocation strategies
## Architecture Details
### Size Classification System
The allocator uses a logarithmic size classification system with 10 levels:
```
Level 0: 64 bytes (2^6)
Level 1: 256 bytes (2^8)
Level 2: 1KB (2^10)
Level 3: 4KB (2^12)
Level 4: 16KB (2^14)
Level 5: 64KB (2^16)
Level 6: 256KB (2^18)
Level 7: 1MB (2^20)
Level 8: 4MB (2^22)
Level 9: 16MB (2^24)
```
Each level follows the pattern: `size = 64 * 4^level`
### Bucket Organization
Each size level is further divided into 3 segments based on exact size requirements:
- **Segment 0**: 1x level size
- **Segment 1**: 2x level size
- **Segment 2**: 3x level size
This provides fine-grained size matching while maintaining O(1) lookup performance.
### External Metadata Design
Unlike traditional allocators that embed metadata within allocated blocks, HLSF stores all metadata externally:
- **Entry Structure (36 bytes)**: Contains position, size, level, and linkage information
- **BucketNode Structure (32 bytes)**: Manages free block organization with integrity verification
- **SLAB Storage**: Both structures are allocated from separate SLAB allocators for optimal cache performance
This design provides several advantages:
- Clean allocation areas free from metadata corruption
- Better cache locality for metadata operations
- Simplified debugging and memory inspection
- No metadata overhead in allocated blocks
## Performance Benchmarks
*Note: The following results are from informal testing and should be considered preliminary.*
### Single-threaded Performance (65,536 operations)
| Operation | Average Time | Throughput | Performance Rating |
|-----------|--------------|------------|-------------------|
| Allocation | 31-40ms | ~2M allocs/sec | Good |
| Free | 2-3ms | ~25M frees/sec | Excellent |
**Test Configuration:**
- Pool Size: 16MB
- Block Sizes: 64-1024 bytes (mixed)
- Platform: x64, .NET 8
- Iterations: 5 rounds
### Memory Efficiency
- **Metadata Overhead**: ~2.8% of total allocation
- **Alignment**: 16-byte default (configurable)
- **Fragmentation**: Low due to intelligent merging algorithms
## Usage Examples
### Basic Allocation
## Quick Use
```csharp ```csharp
// Create allocator with 64MB pool using var hlsf = new HybridLayerSegregatedFitAllocator(64 * 1024 * 1024);
using var allocator = new HybridLayerSegregatedFitAllocator(64 * 1024 * 1024); var h = hlsf.Alloc(1024);
Span<byte> s = h.BufferHandle; s[0] = 42;
// Allocate 1KB block hlsf.Free(h);
var handle = allocator.Alloc(1024);
// Use the allocated memory
Span<byte> memory = handle.BufferHandle;
memory[0] = 42;
// Free the block
allocator.Free(handle);
``` ```
### Custom Alignment ## Benchmarks (this run)
Environment: .NET 8 (Release), x64, single-thread, arena=3072 MiB. Results from `StgSharpDebug/HlsfStressTest`.
```csharp - Free throughput: 1KB ~ 262,144 ★ 27,062,260.6 free/s
// Create allocator with 32-byte alignment - Alloc+free pairs: 256B ~ 5,000,000 ★ 19,256,409.9 ops/s
using var allocator = new HybridLayerSegregatedFitAllocator( - Fragmentation cycle (「512MB): fast recovery in short cycle (see test for details)
byteSize: 32 * 1024 * 1024,
align: 32
);
var handle = allocator.Alloc(256); Representative allocation throughput (higher is better):
// Memory is guaranteed to be 32-byte aligned
```
### Bulk Operations | Size | Ops | alloc/s (no touch) | alloc/s (touch) |
|------|----:|--------------------:|----------------:|
| 64B | 2,000,000 | 7,902,555.2 | 14,176,544.8 |
| 1KB | 1,572,864 | 5,794,161.6 | 7,293,856.5 |
| 4KB | 393,216 | 5,210,132.6 | 6,646,372.3 |
| 32KB | 49,152 | 5,931,432.3 | 7,719,199.1 |
| 256KB | 6,144 | 14,663,484.5 | 10,425,929.1 |
| 512KB | 3,072 | 11,054,336.1 | 16,245,372.8 |
| 1MB | 1,536 | 13,391,456.0 | 9,159,212.9 |
| 2MB | 768 | 17,028,824.8 | 11,962,616.8 |
| 8MB | 192 | 13,061,224.5 | 9,142,857.1 |
| 32MB | 48 | 11,707,317.1 | 9,411,764.7 |
```csharp Notes:
var handles = new List<HybridLayerSegregatedFitAllocationHandle>(); - Peak no-touch at 2MB (「17.0M alloc/s). Peak touch at 512KB (「16.25M alloc/s).
- Non-aligned sizes (e.g., 768B, 3KB) typically lower due to slicing/boundary handling.
// Allocate multiple blocks ## Detailed Design
for (int i = 0; i < 1000; i++)
{
handles.Add(allocator.Alloc((uint)(64 + i % 960))); // 64-1023 bytes
}
// Free all blocks ### Size Classes and Segments
foreach (var handle in handles) - Level sizes: `levelSizeArray = [64, 256, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216]` (64B , 4^L)
{ - Level selection:
allocator.Free(handle); - `GetLevelFromSize(uint S)`: `level = max(0, (log2(S) - 6) / 2)` using `BitOperations.LeadingZeroCount`
} - Segment selection (within a level):
``` - `DetermineSegmentIndex(size, level) = (int)((size - 1) / levelSizeArray[level])` ★ 0/1/2
- Bucket index: `index = 3*level + segment` (for L + 9); overflow bucket at `^1` for level > 9
## API Reference ### Metadata Structures
- `Entry` (external SLAB): linkage in position chain, `Position`, `Size`, `Level`, `State`
- Invariants: near neighbors must form a consistent double-linked ring; `Level` setter validates size vs. level window
- `BucketNode` (in user region): `EntryRef`, `NextLevel`, `PreviousLevel`, `IsInBucket`
- Position chain: circular doubly linked list anchored by sentinel `_spareMemory` (`State.Spare`)
### HybridLayerSegregatedFitAllocator ### Allocation Flow
1) Compute `level` and `segment` from requested `size`
2) Try pop bucket (`TryPopLevelForAllocation(level, segment, out entry)`) and escalate:
- Advance `segment` 0★1★2, then `level++`, until `MaxLevel`
- If still empty: try pop from overflow bucket (L=10, seg=1) or `TryAllocateNew32MBBlock`
3) On success: bound-check the slice offset; set `entry` to `Alloc` using `SetEntryLevel`
4) If remainder exists in the source region, call `SliceMemory(entry->NextNear, remainder)` to produce free chunks into buckets
Main allocator class providing memory management functionality. ### Slicing Logic (`SliceMemory`)
- Inputs: `next` entry position, `sizeToSlice`
- Compute `offset` and `end` relative to base buffer; pick starting level by trailing-zero count of `offset`:
- `i = (BitOperations.TrailingZeroCount(offset) - 6) / 2`, clamped to `+ MaxLevel`
- Three phases to cover [offset, end):
1) Forward levels i..N-1: for each level size `size` and `upper=size*4`, align to `edge=((offset+upper-1)/upper)*upper`, pack `count = ((min(edge,end) - offset) / size)` blocks
2) If `i > MaxLevel`: pack in largest upper window on tail
3) Backward i-1..0: fill remaining tail with smaller sizes
- For each packed region: allocate new `Entry`, insert before `next`, mark `Empty`, and push corresponding `BucketNode` into the `(level, segment)` bucket (`PushBucketToLevel(i, count-1, b)`)
#### Constructors ### Free and Merge (`MergeAndRemoveFromBuckets`)
- Given an `Entry` to free (turned `Empty` by caller):
- Merge left while neighbor `p` is `Empty`, same `Level`, and within boundary (`AssertBoundary`)
- Remove `p` from bucket (`RemoveFromBucket`), expand current, fix `Position`, unlink `p`
- Merge right with neighbor `n` by the same rule
- Optionally promote `Level` if `Size - levelSizeArray[originLevel] * 4`
- Ensure `BucketNode.EntryRef` points to the merged entry; caller re-enqueues into the proper bucket
```csharp ### Buckets
public HybridLayerSegregatedFitAllocator(nuint byteSize) - `PushBucketToLevel(level, segment, node)`: push to bucket head, set `IsInBucket`, maintain prev/next
public HybridLayerSegregatedFitAllocator(nuint byteSize, int align) - `TryPopLevel(level, segment, out node)`: pop head; if empty, return false
``` - `RemoveFromBucket(level, segment, node)`: unlink from middle or use `TryPopLevel` if it is the head
#### Methods ### Large Allocations and Spare Memory
- Requests > 32MB use `NativeMemory.AlignedAlloc(size, align)` and mark `State.Large`
- When buckets are empty, `TryAllocateNew32MBBlock` carves a 32MB region from `_spareMemory`, builds an `Empty` entry, and advances the spare pointer
```csharp ### Alignment
public HybridLayerSegregatedFitAllocationHandle Alloc(uint size) - Default `_align = 16`; `AlignOfLevel(level)` returns `min(levelSizeArray[level], _align)`
public void Free(HybridLayerSegregatedFitAllocationHandle handle) - Internal blocks align to level boundaries by design of slicing/edge calculations
public void Dispose()
```
### HybridLayerSegregatedFitAllocationHandle ### Complexity and Invariants
- Allocate/free are O(1) on average with bounded levels (+10) and constant-time bucket ops
- Invariants guarded by exceptions (e.g., `HLSFPositionChainBreakException`) and pointer consistency checks
Handle representing an allocated memory block. ## Profiling (hotspots, current workloads)
- `SliceMemory` 「22% (self 「17%) ! slicing and bucket enqueue dominate allocation path
- `MergeAndRemoveFromBuckets` 「13% (self 「12%) ! merge + removal dominate free path
- `Collect` 「7.6% (self 「7.5%) ! low overall cost
#### Properties Suggested focus: reduce per-slice math and bucket ops; add fast-paths for fully aligned regions; batch/lazy removals after merges.
```csharp
public readonly byte* Pointer { get; }
public readonly Span<byte> BufferHandle { get; }
public readonly uint AllocSize { get; }
public readonly ref byte this[int index] { get; }
```
## Thread Safety
The current implementation is **single-threaded**. For multi-threaded applications, external synchronization is required. A concurrent version is planned for future releases.
## Comparison with Other Allocators
### vs TLSF (Two-Level Segregated Fit)
| Aspect | HLSF | TLSF | Notes |
|--------|------|------|-------|
| Allocation Speed | ~2M ops/sec | ~3-5M ops/sec | HLSF has optimization potential |
| Deallocation Speed | ~25M ops/sec | ~10-15M ops/sec | **HLSF advantage** |
| Memory Overhead | ~2.8% | ~1-2% | HLSF uses external metadata |
| Time Complexity | O(1) | O(1) | Equal |
| Code Complexity | Moderate | Low | HLSF more sophisticated |
### Strengths
- Superior deallocation performance
- Clean separation of data and metadata
- Configurable alignment support
- Excellent for allocation-heavy workloads
### Areas for Improvement
- Allocation speed optimization
- Memory overhead reduction
- Concurrent implementation
## Implementation Notes
### Alignment Guarantees
- Minimum alignment: 16 bytes
- All allocations respect specified alignment
- Internal blocks aligned to 64-byte boundaries for optimal cache performance
### Merging Strategy
The allocator employs an aggressive merging strategy:
1. **Immediate Merging**: Adjacent free blocks are merged during deallocation
2. **Boundary Checking**: Ensures merging respects level boundaries
3. **Bidirectional Merging**: Checks both forward and backward neighbors
4. **Level Promotion**: Merged blocks may be promoted to higher levels
## Future Roadmap
### Planned Optimizations
1. **Performance Enhancements**
- Lookup table optimization for size classification
- Bucket operation improvements
- SIMD utilization for bulk operations
2. **Concurrent Support**
- Thread-local caches
- Lock-free bucket operations
- NUMA-aware allocation
3. **Advanced Features**
- Adaptive size level adjustment
- Memory pressure handling
- Statistics and profiling support
### Target Performance Goals
- Allocation Speed: 4-6M ops/sec (single-threaded)
- Memory Overhead: <1.5%
- Fragmentation Rate: <3%
- Concurrent Scaling: Linear up to 16 threads
## Building and Testing
The HLSF allocator is part of the StgSharp.Common library and requires:
- .NET 8 or later
- x64 architecture (no x86 support planned)
- Windows/Linux
### Running Benchmarks
```csharp
// See StgSharpDebug/Program.cs for comprehensive benchmark suite
var allocator = new HybridLayerSegregatedFitAllocator(16 * 1024 * 1024);
// Run allocation/deallocation cycles...
```
## License
This implementation is part of the StgSharp project and follows the project's licensing terms.
## Contributing
Performance improvements, bug fixes, and documentation enhancements are welcome. Please ensure all changes maintain the O(1) performance guarantees and include appropriate benchmarks.
+8 -4
View File
@@ -65,7 +65,6 @@ namespace StgSharp
uint size = sz > MaxAllocSize ? MaxAllocSize : sz; uint size = sz > MaxAllocSize ? MaxAllocSize : sz;
BenchAllocThroughput(allocator, size, touch:false); BenchAllocThroughput(allocator, size, touch:false);
allocator.Collect(); allocator.Collect();
allocator.DumpPositionChain();
GC.Collect(); GC.Collect();
GC.WaitForPendingFinalizers(); GC.WaitForPendingFinalizers();
} }
@@ -130,7 +129,9 @@ namespace StgSharp
} }
sw.Stop(); sw.Stop();
double opsPerSec = pairs / sw.Elapsed.TotalSeconds; double opsPerSec = pairs / sw.Elapsed.TotalSeconds;
Console.WriteLine($" size={Fmt(blockSize),7} pairs={pairs,9} alloc+free/s={opsPerSec,12:0.0}");
// Left align and widen columns
Console.WriteLine($" size={Fmt(blockSize),-12} pairs={pairs,-12} alloc+free/s={opsPerSec,-16:0.0}");
} }
private static void BenchAllocThroughput( private static void BenchAllocThroughput(
@@ -171,7 +172,8 @@ namespace StgSharp
allocator.Free(h); allocator.Free(h);
} }
Console.WriteLine($" size={Fmt(blockSize),7} ops={ops,9} alloc/s={allocOpsPerSec,12:0.0} touch={touch}"); // Left align and widen columns
Console.WriteLine($" size={Fmt(blockSize),-12} ops={ops,-12} alloc/s={allocOpsPerSec,-16:0.0} touch={touch,-5}");
} }
private static void BenchFragmentationCycle( private static void BenchFragmentationCycle(
@@ -244,7 +246,9 @@ namespace StgSharp
sw.Stop(); sw.Stop();
double freesPerSec = count / sw.Elapsed.TotalSeconds; double freesPerSec = count / sw.Elapsed.TotalSeconds;
Console.WriteLine($" size={Fmt(blockSize),7} count={count,9} free/s={freesPerSec,12:0.0}");
// Left align and widen columns
Console.WriteLine($" size={Fmt(blockSize),-12} count={count,-12} free/s={freesPerSec,-16:0.0}");
} }
private static string Fmt(uint bytes) private static string Fmt(uint bytes)