hlsf error is solved. HLSF is now full functional available.
This commit is contained in:
@@ -1,271 +1,109 @@
|
||||
# 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
|
||||
|
||||
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.
|
||||
|
||||
## Key Features
|
||||
|
||||
### 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
|
||||
## Key Points
|
||||
- O(1) allocate/free with 10 levels: 64B ★ 16MB; each level split into 1~/2~/3~ segments
|
||||
- External metadata (`Entry`, `BucketNode`) in dedicated SLABs; user blocks have no headers
|
||||
- Configurable alignment (-16B); large block cap per allocation: 32MB
|
||||
- Single-threaded implementation (external sync required for multi-threaded use)
|
||||
|
||||
## Quick Use
|
||||
```csharp
|
||||
// Create allocator with 64MB pool
|
||||
using var allocator = new HybridLayerSegregatedFitAllocator(64 * 1024 * 1024);
|
||||
|
||||
// Allocate 1KB block
|
||||
var handle = allocator.Alloc(1024);
|
||||
|
||||
// Use the allocated memory
|
||||
Span<byte> memory = handle.BufferHandle;
|
||||
memory[0] = 42;
|
||||
|
||||
// Free the block
|
||||
allocator.Free(handle);
|
||||
using var hlsf = new HybridLayerSegregatedFitAllocator(64 * 1024 * 1024);
|
||||
var h = hlsf.Alloc(1024);
|
||||
Span<byte> s = h.BufferHandle; s[0] = 42;
|
||||
hlsf.Free(h);
|
||||
```
|
||||
|
||||
### Custom Alignment
|
||||
## Benchmarks (this run)
|
||||
Environment: .NET 8 (Release), x64, single-thread, arena=3072 MiB. Results from `StgSharpDebug/HlsfStressTest`.
|
||||
|
||||
```csharp
|
||||
// Create allocator with 32-byte alignment
|
||||
using var allocator = new HybridLayerSegregatedFitAllocator(
|
||||
byteSize: 32 * 1024 * 1024,
|
||||
align: 32
|
||||
);
|
||||
- Free throughput: 1KB ~ 262,144 ★ 27,062,260.6 free/s
|
||||
- Alloc+free pairs: 256B ~ 5,000,000 ★ 19,256,409.9 ops/s
|
||||
- Fragmentation cycle (「512MB): fast recovery in short cycle (see test for details)
|
||||
|
||||
var handle = allocator.Alloc(256);
|
||||
// Memory is guaranteed to be 32-byte aligned
|
||||
```
|
||||
Representative allocation throughput (higher is better):
|
||||
|
||||
### 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
|
||||
var handles = new List<HybridLayerSegregatedFitAllocationHandle>();
|
||||
Notes:
|
||||
- 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
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
handles.Add(allocator.Alloc((uint)(64 + i % 960))); // 64-1023 bytes
|
||||
}
|
||||
## Detailed Design
|
||||
|
||||
// Free all blocks
|
||||
foreach (var handle in handles)
|
||||
{
|
||||
allocator.Free(handle);
|
||||
}
|
||||
```
|
||||
### Size Classes and Segments
|
||||
- Level sizes: `levelSizeArray = [64, 256, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216]` (64B , 4^L)
|
||||
- Level selection:
|
||||
- `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
|
||||
public HybridLayerSegregatedFitAllocator(nuint byteSize)
|
||||
public HybridLayerSegregatedFitAllocator(nuint byteSize, int align)
|
||||
```
|
||||
### Buckets
|
||||
- `PushBucketToLevel(level, segment, node)`: push to bucket head, set `IsInBucket`, maintain prev/next
|
||||
- `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
|
||||
public HybridLayerSegregatedFitAllocationHandle Alloc(uint size)
|
||||
public void Free(HybridLayerSegregatedFitAllocationHandle handle)
|
||||
public void Dispose()
|
||||
```
|
||||
### Alignment
|
||||
- Default `_align = 16`; `AlignOfLevel(level)` returns `min(levelSizeArray[level], _align)`
|
||||
- Internal blocks align to level boundaries by design of slicing/edge calculations
|
||||
|
||||
### 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
|
||||
|
||||
```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.
|
||||
Suggested focus: reduce per-slice math and bucket ops; add fast-paths for fully aligned regions; batch/lazy removals after merges.
|
||||
Reference in New Issue
Block a user