cad.net 最快的字典SwissTable

SwissTable

各种策略

1,主要参考了谷歌的SwissTable,它是在开放寻址法上面的一种极端优化.
我没有看过谷歌的代码,只是凭借网络文章自己用手机敲的C#版本,可能实现得有点不一样.
是用在线NET80环境验证的,
不过只是用到Sse,应该可以向下扩展.

2,SwissTable数据结构
它描述了A数组(table/分片)链接着B数组(group),B组是定长是SIMD宽度,
也就是它本质上是一个二维数组结构.
为什么不直接用数组呢?
有没有可能我们只是对数组封装了规则?嘿嘿.
不过更多时候其实是为了实现rehash这种东西.

B组不要用Vector256,因为权衡了这个128长度.128/16=8bit.
因此我们选用Vector128.
8bit表示用byte类型作为一个短hash,叫做h2.
这样SIMD一次性可以比较16个h2,以此实现加速.
16个表示说明解决冲突足够了,
如果不满足的话,会直接++处理下一个分组,分组可以SIMD预取.

其中byte最高位1bit被占用作为byte符号位,最高位是1,
所以其实只是用了7bit.

3,申请内存和清理.
C++编程习惯是无根持有堆对象,没有GC去回收,
清理的时候可以只清空meta数组,不需要清理key,value数组.

C#虽然也能做到无根持有,无非就是非托管堆申请内存.
但是习惯是要容器持有对象强引用,不然KV两个对象会被GC回收.
也就是我们需要Clear key,value数组,否则不就是移除了对象还在持有对象强引用了?
这样一搞速度就低了啊,还是蛮麻烦的.

_table使用了alloc进行申请非托管内存,并且不清零策略.
非托管堆还没有GC扫描它,没有大对象扫描,使得可以减少STW时间.
不过无法在非安全上下文中使用yield来构造迭代器,所以要用传统的枚举器模式.

4,扩容
rehash不能用realloc,因为存在新旧搬迁.
absl::flat_hash_map是动态重新求hashcode,
储存在h1Array有储存成本...并且可变类存在致命问题.
例如string的hashcode缓存其实是对的,不过通常是实现类上面进行,而非容器进行.
所以我们也要动态求hashcode

没实现渐进式扩容,毕竟它不是并发容器.
还没做...

5,go语言存在缓存失效问题,
https://cloud.tencent.com/developer/article/2523101
我设计本来就是KV分离数组

6,微软在net每个进程都是做了随机值做base,防止别人攻击.
并且提供默认配置可关闭,因为分布式求hash要相同才行.
还没做...

7,为了省电,分组跳转上面我们不用除法器%,而是保证2次方的数组长度,用&来进行取余数
但用素数长度可以增加稀疏性,就要用%了?.
Google的测试表明在良好哈希函数下,素数优势不明显,
位运算的收益明显大于素数带来的分布改善.

9,缩容
因为字典通常都是不缩容的,可以得到更好的性能.
所以像google实现的是手动缩容.
我实现了配置参数上面决定手动和自动缩容.

10,惰性分配
首次是1个组(16个槽位),并没有首次0组.

11,居然被AI耍了,把hashcode | 0x80,害我以为不能写key == 0.

瑞士表的数据结构

结构体数组只需要一次解引用(我实现的).
类数组就需要两次解引用.

_table (Group*)
│
├─ _table[0] (Group实例,结构体)
│  ├─ Metadata → byte[16]  16字节
│  ├─ Keys*    → 指针,独立分配的 TKey[16]   8字节
│  ├─ Values*  → 指针,独立分配的 TValue[16]  8字节
│  └─ H1Array  → 内联的 long[16]   128字节 (可变类型需要每次重新求,废弃)
│
├─ _table[1] (Group实例)
│  ├─ Metadata    → ...
│  └─ ...
└─ ...

为什么 SwissTable 可以不使用 unmanaged 约束

潜在问题

当执行 map.Add("string", 1) 时,字符串"string"在托管堆上分配
指向该key的字符串的指针被存储在非托管内存中
GC会跟踪这个引用,确保字符串不会被错误回收.
但是它会存在风险.

对于引用类型(类)获取其指针需要特殊处理,
因为类的实例存储在托管堆上,并且可能被垃圾回收器移动.
以下是几种获取类实例指针的方法:

1. 使用 GCHandle 固定对象(推荐方法)
private static unsafe IntPtr GetClassPtr<T>(T obj) where T : class { GCHandle handle = GCHandle.Alloc(obj, GCHandleType.Pinned); try { return handle.AddrOfPinnedObject(); } finally { handle.Free(); } } 

如果同一个对象每次调用 GCHandle.Alloc() 都返回不同的  GCHandle ,
那么直接用  GCHandle  作为键会导致哈希表无法正确查找,
因为相同的对象会生成不同的键(GCHandle)导致  Get  操作失败.

2. 使用 fixed 语句(仅适用于包含非托管类型的类)
public unsafe class UnmanagedContainer { public int Value; public static IntPtr GetPointer(UnmanagedContainer obj) { fixed (void* p = &obj.Value) { return (IntPtr)p; } } } 

3. 使用 Unsafe 类(高级用法,需谨慎,它是即用即弃,也就是临时)
private static unsafe IntPtr GetClassPtrUnsafe<T>(T obj) where T : class { return (IntPtr)Unsafe.AsPointer(ref obj); } 

重要注意事项:
固定对象会影响垃圾回收器性能,应尽快释放
获取的指针只在对象被固定期间有效
修改指针指向的内存可能破坏类型安全性
对于纯托管类,无法直接获取其内部结构的指针
对于大多数情况,GCHandle 是最安全可靠的方法.
如果确实需要频繁操作类实例的指针,考虑使用非托管内存或值类型.

不如直接用Dictionary<>持有托管类型,然后做一层转发...

GC 压缩与非托管内存中的托管引用

你提出了一个非常关键的问题!确实,GC 的压缩(compaction)会移动托管对象并更新所有引用,那么为什么存储在非托管内存中的托管引用不会出问题呢?让我们深入分析:

GC 如何处理非托管内存中的托管引用

.NET 运行时确实有机制来处理这种情况,主要通过以下方式:

a) GCHandle 机制(虽然这里没有显式使用)
虽然这个实现没有显式使用 GCHandle,但类似的概念在底层工作。当托管对象被非托管代码引用时,GC 需要知道这些"外部引用"。

b) 关键点:非托管内存本身被 GC 跟踪
在这个实现中,整个 SwissTable 类是一个托管对象,它持有非托管内存的指针(_table)。GC 知道:

  • 这个托管对象(SwissTable 实例)是存活的
  • 这个托管对象"拥有"某些非托管内存
  • 这些非托管内存中可能包含托管引用

为什么不会出问题

具体到这个实现:
非托管内存中的托管引用被 GC 视为根(root):

  • 因为 SwissTable 是托管对象,它内部指针指向的非托管内存被视为"可能包含托管引用"
  • GC 会扫描这些区域,查找看起来像托管引用的指针

GC 压缩时的处理:
当 GC 压缩堆并移动对象时,它会:

  • 更新所有托管代码中的引用
  • 扫描非托管内存中的可能引用并更新它们
    这是通过 GC 的"固定"(pinning)机制实现的

潜在风险与限制

虽然这种设计可以工作,但有重要限制:

性能影响

  • GC 必须扫描非托管内存中的可能引用,这会增加 GC 负担
  • 比纯 unmanaged 类型或显式使用 GCHandle 效率低

正确性依赖

  • 要求非托管内存中的托管引用必须"看起来像"真正的托管引用
  • 如果内存被错误解释(如整数被误认为引用),会导致问题

跨平台考虑
在某些特殊环境(如某些嵌入式系统)中,这种隐式行为可能不被支持

更安全的替代方案

生产代码通常会更明确地处理这种边界:

使用 GCHandle 显式固定

GCHandle handle = GCHandle.Alloc(obj, GCHandleType.Normal);
IntPtr ptr = GCHandle.ToIntPtr(handle); // 存储 ptr 到非托管内存

使用 unmanaged 约束(如果可能)

public sealed unsafe class SwissTable<TKey, TValue> where TKey : unmanaged where TValue : unmanaged

使用 Span 和 MemoryMarshal 进行安全转换

为什么这个示例能工作,这个示例能工作是因为:

  • GC 的保守性:.NET GC 会保守地扫描非托管内存中的可能引用
  • 实现细节:键和值存储为指针,GC 能识别这些是有效的引用
  • 生命周期管理:SwissTable 是托管对象,确保非托管内存中的引用在表存活期间有效

结论
你完全正确地指出了 GC 压缩的问题。虽然这个实现可以工作(得益于 GC 的保守扫描),但在生产环境中:

  • 对于性能关键代码,应该使用 unmanaged 约束
  • 对于需要存储任意类型的场景,应该显式使用 GCHandle
    这种"隐式"依赖 GC 行为的设计虽然方便,但不够健壮。
    这种设计在教育示例中可以接受,但在生产代码中需要更严谨的处理方式。

代码

最少代码改为托管类型

public unsafe struct Group<TKey, TValue> {
    public Vector128<byte> Metadata;
    private GCHandle _keysHandle;   // 托管数组的GC句柄,被GC扫描
    private GCHandle _valuesHandle;

    public TKey* Keys {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => (TKey*)_keysHandle.AddrOfPinnedObject();
    }

    public TValue* Values {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => (TValue*)_valuesHandle.AddrOfPinnedObject();
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Group<TKey, TValue>* Create() {
        var group = (Group<TKey, TValue>*)NativeMemory.Alloc(
            (nuint)sizeof(Group<TKey, TValue>), (nuint)16);

        // 初始化固定住的托管数组
        group->_keysHandle = GCHandle.Alloc(
            new TKey[SwissConfig.GroupSize], 
            GCHandleType.Pinned);

        group->_valuesHandle = GCHandle.Alloc(
            new TValue[SwissConfig.GroupSize], 
            GCHandleType.Pinned);

        group->Metadata = Vector128.Create(SwissConfig.Empty);
        return group;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void Destroy() {
        _keysHandle.Free();  // 释放托管引用
        _valuesHandle.Free();
        NativeMemory.Free(this);
    }
}

SwissTable

using System;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using System.Threading.Tasks;
using System.Diagnostics;

public static class SwissConfig {
    public const byte Empty = 0x80;
    public const byte Tombstone = 0xFF;
    public const float MinCap = 0.25f;
    public const float MaxCap = 0.8f;
    public const int GroupSize = 16;  // 其实就是 Vector128<byte>.Count 为了内联直接写常量 //
}

[StructLayout(LayoutKind.Sequential, Pack = 16)]
public unsafe struct Group<TKey, TValue> {
    // 热路径在第一位
    public Vector128<byte> Metadata;
    public TKey* Keys;
    public TValue* Values;
    // public fixed long H1Array[16]; 容器不要缓存此对象,
    // 因为rehash要重计算才对,否则可变对象缓存是致命的,
    // 不可变对象应该实现类中用字段进行缓存hashcode

    // 创建并初始化一个新的Group
    public static Group<TKey, TValue>* Create() {
        Console.WriteLine("创建新Group");
        var group = (Group<TKey, TValue>*)NativeMemory.Alloc(
            (nuint)sizeof(Group<TKey, TValue>), (nuint)16);
        if (group == null)
            throw new OutOfMemoryException("Failed to allocate memory for Group");

        group->Keys = (TKey*)NativeMemory.Alloc((nuint)(SwissConfig.GroupSize * sizeof(TKey)), (nuint)sizeof(TKey));
        if (group->Keys == null) {
            NativeMemory.Free(group);
            throw new OutOfMemoryException("Failed to allocate memory for Keys");
        }

        group->Values = (TValue*)NativeMemory.Alloc((nuint)(SwissConfig.GroupSize * sizeof(TValue)), (nuint)sizeof(TValue));
        if (group->Values == null) {
            NativeMemory.Free(group->Keys);
            group->Keys = null;
            NativeMemory.Free(group);
            throw new OutOfMemoryException("Failed to allocate memory for Values");
        }

        group->Metadata = Vector128.Create(SwissConfig.Empty);

/*
        long* h1s = group->H1Array;
        for (int i = 0; i < SwissConfig.GroupSize; i += 2) {
            Sse2.Store(h1s + i, Vector128<long>.Zero);
        }
*/
        return group;
    }

    // 清空Group的元数据
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void Clear() {
        // 这两个是一样的
        // Metadata = Vector128.Create(SwissConfig.Empty);
        fixed (Vector128<byte>* pMetadata = &Metadata) {
            Sse2.Store((byte*)pMetadata, Vector128.Create(SwissConfig.Empty));
        }
    }

    // 破坏成员,用于回收
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void Destroy() {
        if (Keys != null) {
            NativeMemory.Free(Keys);
            Keys = null;
        }
        if (Values != null) {
            NativeMemory.Free(Values);
            Values = null;
        }
    }

    // 预取优化方法
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void PrefetchNextGroup(Group<TKey, TValue>* nextGroup) {
        if (Sse.IsSupported) {
            Sse.Prefetch0((byte*)&nextGroup->Metadata);
            Sse.Prefetch0((byte*)nextGroup->Keys);
            Sse.Prefetch0((byte*)nextGroup->Values);
           // Sse.Prefetch2((byte*)nextGroup->H1Array);
        }
    }

    // 在Group中查找匹配的槽位
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public readonly int FindSlot(byte h2, long h1, TKey key, IEqualityComparer<TKey> comparer) {
        Console.WriteLine($"FindSlot在Group中查找 h2={h2}, h1={h1}, key={key}");
        var match = Vector128.Create(h2);
        var eq = Sse2.CompareEqual(Metadata, match);
        int mask = Sse2.MoveMask(eq);
        Console.WriteLine($"FindSlot的SIMD比较结果 mask={mask:X}");

        while (mask != 0) {
            int slot = BitOperations.TrailingZeroCount(mask);
            // Console.WriteLine($"FindSlot检查槽位 {slot}, h1={H1Array[slot]}, key={Keys[slot]}");
            // 这里不需要比较 H1Array[slot] == h1 &&  了
            if (comparer.Equals(Keys[slot], key)) {
                Console.WriteLine($"FindSlot在槽位 {slot} 找到匹配项");
                return slot;
            }
            mask &= ~(1 << slot);
            Console.WriteLine($"FindSlot更新后 mask={mask:X}");
        }
        Console.WriteLine("FindSlot未找到匹配项");
        return -1;
    }

    // 查找空槽或墓碑槽
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public readonly int FindEmptyOrTombstone() {
        Console.WriteLine("查找空槽或墓碑槽");
        var empty = Vector128.Create(SwissConfig.Empty);
        var tombstone = Vector128.Create(SwissConfig.Tombstone);
        
        var eqEmpty = Sse2.CompareEqual(Metadata, empty);
        var eqTombstone = Sse2.CompareEqual(Metadata, tombstone);
        var combined = Sse2.Or(eqEmpty, eqTombstone);
        
        int mask = Sse2.MoveMask(combined);
        Console.WriteLine($"空槽/墓碑槽 mask={mask:X}");
        return mask == 0 ? -1 : BitOperations.TrailingZeroCount(mask);
    }

    // 设置指定槽位的元数据
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void SetMetadata(int slot, byte value) {
        // Console.WriteLine($"设置槽位 {slot} 元数据为 {value}");
        fixed (Vector128<byte>* pMetadata = &Metadata) {
            ((byte*)pMetadata)[slot] = value;
        }
    }

    // 获取指定槽位的元数据
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public readonly byte GetMetadata(int slot) {
        byte value;
        fixed (Vector128<byte>* pMetadata = &Metadata) {
            value = ((byte*)pMetadata)[slot];
        }
        // Console.WriteLine($"获取槽位 {slot} 元数据: {value}");
        return value;
    }
}

public sealed unsafe class SwissTable<TKey, TValue> : IDisposable,
    IEnumerable<KeyValuePair<TKey, TValue>>
/* 最好加入约束
    where TKey : unmanaged
    where TValue : unmanaged
*/
{
    private Group<TKey, TValue>* _table;
    private int _groupCount;
    private int _count;
    private int _tombstones;
    private readonly IEqualityComparer<TKey> _comparer;

    public int Count => _count;
    public int TombstoneCount() => _tombstones;

    // 静态配置变量,控制是否自动缩容
    // 默认手动缩容,因为字典通常不缩容可以得到更好的性能
    public bool AutoShrink { get; set; } = false;

    // 构造函数: 使用默认比较器
    public SwissTable() : this(EqualityComparer<TKey>.Default, 1) { }
    
    // 构造函数: 指定比较器和初始容量
    public SwissTable(IEqualityComparer<TKey> comparer, int groupNum = 1) {
        Console.WriteLine($"创建SwissTable,初始组数: {groupNum}");
        _comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
        Initialize(groupNum > 0 ? groupNum : 1);
    }

    ~SwissTable() {
        Dispose(false);
    }

    // 释放资源
    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // 释放资源实现
    private void Dispose(bool disposing) {
        if (_table == null) return;
        
        Console.WriteLine($"释放SwissTable,组数: {_groupCount}");
        for (int i = 0; i < _groupCount; i++) {
            _table[i].Destroy();
        }
        NativeMemory.Free(_table);
        _table = null;
        _groupCount = 0;
        _count = 0;
        _tombstones = 0;
    }

    // 初始化哈希表
    private void Initialize(int groupNum) {
        Console.WriteLine($"初始化SwissTable,请求组数: {groupNum}");
        
        // 释放现有表
        if (_table != null) {
            Console.WriteLine("释放现有表...");
            int oldGroupCount = _groupCount;
            for (int i = 0; i < oldGroupCount; i++) {
                _table[i].Destroy();
            }
            NativeMemory.Free(_table);
            _table = null;
            _groupCount = 0;
        }

        // 初始化新表
        _groupCount = (int)BitOperations.RoundUpToPowerOf2((uint)Math.Max(1, groupNum));
        Console.WriteLine($"实际分配的组数: {_groupCount}");
        
        _table = (Group<TKey, TValue>*)NativeMemory.Alloc((nuint)(_groupCount * sizeof(Group<TKey, TValue>)), (nuint)16);
        if (_table == null)
            throw new OutOfMemoryException("Failed to allocate memory for SwissTable");

        try {
            for (int i = 0; i < _groupCount; i++) {
                Console.WriteLine($"初始化Group {i}");
                _table[i] = *Group<TKey, TValue>.Create();
            }
        }
        catch {
            Console.WriteLine("初始化失败,清理资源...");
            for (int i = 0; i < _groupCount; i++) {
                _table[i].Destroy();
            }
            NativeMemory.Free(_table);
            _table = null;
            _groupCount = 0;
            throw;
        }

        _count = 0;
        _tombstones = 0;
        Console.WriteLine($"初始化完成,总组数: {_groupCount}");
    }

    // 计算键的哈希值(h1和h2)
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private (long h1, byte h2) GetHashes(TKey key) {
        if (Unsafe.IsNullRef(ref key)) throw new ArgumentNullException(nameof(key));

        int fullHash = _comparer.GetHashCode(key) & 0x7FFFFFFF;
        ulong hash = (ulong)fullHash;
        hash ^= (hash >> 23) ^ (hash >> 47);
        hash = hash * 31;
        long h1 = (long)(hash >> 7) & 0x1FFFFFFFFFFFFFFL;
        byte h2 = (byte)(hash & 0x7F);
        Console.WriteLine($"键 {key} 的哈希值: h1={h1}, h2={h2}");
        return (h1, h2);
    }

    // 尝试获取指定键的值
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool TryGetValue(TKey key, out TValue value) {
        if (Unsafe.IsNullRef(ref key)) throw new ArgumentNullException(nameof(key));
        if (_table == null) {
            value = default;
            return false;
        }

        Console.WriteLine($"****尝试获取键 {key} 的值");
        var (h1, h2) = GetHashes(key);
        var groupIndex = h1 & (_groupCount - 1);
        Console.WriteLine($"初始组索引: {groupIndex}");

        // 预取第一个Group
        Group<TKey, TValue>.PrefetchNextGroup(&_table[groupIndex]);

        for (int i = 0; i < _groupCount; i++) {
            var probeIndex = (groupIndex + i) & (_groupCount - 1);
            Console.WriteLine($"探测组 {probeIndex}");
            
            // 预取下一个Group(如果有)
            if (i + 1 < _groupCount) {
                var nextProbeIndex = (groupIndex + i + 1) & (_groupCount - 1);
                Group<TKey, TValue>.PrefetchNextGroup(&_table[nextProbeIndex]);
            }

            int slot = _table[probeIndex].FindSlot(h2, h1, key, _comparer);
           
            if (slot >= 0) {
                value = _table[probeIndex].Values[slot];
                Console.WriteLine($"找到键 {key} 的值: {value}");
                return true;
            }
            
            if (_table[probeIndex].FindEmptyOrTombstone() >= 0)
                break;
        }

        value = default;
        Console.WriteLine($"未找到键 {key} 的值");
        return false;
    }

    public bool TryGetValueRef(TKey key, ref TValue value) {
        if (Unsafe.IsNullRef(ref key)) throw new ArgumentNullException(nameof(key));
        if (_table == null) {
            value = ref Unsafe.NullRef<TValue>();
            return false;
        }

        Console.WriteLine($"****尝试获取键 {key} 的引用");
        var (h1, h2) = GetHashes(key);
        var groupIndex = h1 & (_groupCount - 1);

        // 预取第一个Group
        Group<TKey, TValue>.PrefetchNextGroup(&_table[groupIndex]);

        for (int i = 0; i < _groupCount; i++) {
            var probeIndex = (groupIndex + i) & (_groupCount - 1);
            
            // 预取下一个Group(如果有)
            if (i + 1 < _groupCount) {
                var nextProbeIndex = (groupIndex + i + 1) & (_groupCount - 1);
                Group<TKey, TValue>.PrefetchNextGroup(&_table[nextProbeIndex]);
            }

            int slot = _table[probeIndex].FindSlot(h2, h1, key, _comparer);
            if (slot >= 0) {
                value = ref _table[probeIndex].Values[slot];
                Console.WriteLine($"找到键 {key} 的引用");
                return true;
            }
            
            if (_table[probeIndex].FindEmptyOrTombstone() >= 0)
                break;
        }

        value = ref Unsafe.NullRef<TValue>();
        Console.WriteLine($"未找到键 {key} 的引用");
        return false;
    }

    // 检查是否包含指定键
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool ContainsKey(TKey key) {
        Console.WriteLine($"检查是否包含键 {key}");
        return TryGetValue(key, out _);
    }

    // 尝试添加键值对
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool TryAdd(TKey key, TValue value) {
        Console.WriteLine($"****尝试添加键: {key}, 值: {value}");
        if (Unsafe.IsNullRef(ref key)) throw new ArgumentNullException(nameof(key));
        var (h1, h2) = GetHashes(key);
        return TryInsert(key, value, h1, h2);
    }

    // 内部插入实现
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool TryInsert(TKey key, TValue value, long h1, byte h2) {
        if (ShouldExpand()) {
            Console.WriteLine($"需要扩容,当前计数: {_count}, 组数: {_groupCount}");
            Resize();
        }

        var groupIndex = h1 & (_groupCount - 1);
        Console.WriteLine($"初始组索引: {groupIndex}");

        // 预取第一个Group
        Group<TKey, TValue>.PrefetchNextGroup(&_table[groupIndex]);

        for (int i = 0; i < _groupCount; i++) {
            var probeIndex = (groupIndex + i) & (_groupCount - 1);
            Console.WriteLine($"探测组 {probeIndex}");
            
            // 预取下一个Group(如果有)
            if (i + 1 < _groupCount) {
                var nextProbeIndex = (groupIndex + i + 1) & (_groupCount - 1);
                Group<TKey, TValue>.PrefetchNextGroup(&_table[nextProbeIndex]);
            }

            ref Group<TKey, TValue> group = ref _table[probeIndex];
            
            int existingSlot = group.FindSlot(h2, h1, key, _comparer);
            if (existingSlot >= 0) {
                Console.WriteLine($"键 {key} 已存在于组 {probeIndex} 槽位 {existingSlot}");
                return false;
            }
            
            int slot = group.FindEmptyOrTombstone();
            if (slot >= 0) {
                Console.WriteLine($"在组 {probeIndex} 槽位 {slot} 插入键 {key}");
                Insert(ref group, slot, key, value, h1, h2);
                return true;
            }
            else {
                Console.WriteLine($"组 {probeIndex} 已满");
            }
        }
        
        Console.WriteLine("所有组都已满,需要再次扩容...程序写错才会触发");
        Resize();
        return TryInsert(key, value, h1, h2);
    }

    // 内部插入,实际插入
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private void Insert(ref Group<TKey, TValue> group, int slot, TKey key, TValue value, long h1, byte h2) {
        if (group.GetMetadata(slot) == SwissConfig.Tombstone) {
            Console.WriteLine($"替换墓碑槽位 {slot}");
            _tombstones--;
        }

        // group.H1Array[slot] = h1;
        group.Keys[slot] = key;
        group.Values[slot] = value;
        group.SetMetadata(slot, h2);
        _count++;
        
        Console.WriteLine($"插入成功,当前计数: {_count}, 墓碑数: {_tombstones}");
    }

    // 添加或更新键值对
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool AddOrUpdate(TKey key, TValue value) {
        if (Unsafe.IsNullRef(ref key)) throw new ArgumentNullException(nameof(key));
        Console.WriteLine($"添加或更新键: {key}, 值: {value}");
        var (h1, h2) = GetHashes(key);
        
        var groupIndex = h1 & (_groupCount - 1);
        Console.WriteLine($"初始组索引: {groupIndex}");

        // 预取第一个Group
        Group<TKey, TValue>.PrefetchNextGroup(&_table[groupIndex]);

        for (int i = 0; i < _groupCount; i++) {
            var probeIndex = (groupIndex + i) & (_groupCount - 1);
            
            // 预取下一个Group(如果有)
            if (i + 1 < _groupCount) {
                var nextProbeIndex = (groupIndex + i + 1) & (_groupCount - 1);
                Group<TKey, TValue>.PrefetchNextGroup(&_table[nextProbeIndex]);
            }

            ref Group<TKey, TValue> group = ref _table[probeIndex];
            
            int slot = group.FindSlot(h2, h1, key, _comparer);
            if (slot >= 0) {
                Console.WriteLine($"更新组 {probeIndex} 槽位 {slot} 的值");
                group.Values[slot] = value;
                return false;
            }
            
            slot = group.FindEmptyOrTombstone();
            if (slot >= 0) {
                Console.WriteLine($"在组 {probeIndex} 槽位 {slot} 插入新键值对");
                Insert(ref group, slot, key, value, h1, h2);
                return true;
            }
        }

        Console.WriteLine("所有组都已满,需要扩容");
        Resize();
        return AddOrUpdate(key, value);
    }

    // 移除指定键
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool Remove(TKey key) {
        if (Unsafe.IsNullRef(ref key)) throw new ArgumentNullException(nameof(key));
        Console.WriteLine($"****尝试移除键: {key}");
        if (_table == null) return false;

        var (h1, h2) = GetHashes(key);
        var groupIndex = h1 & (_groupCount - 1);
        Console.WriteLine($"初始组索引: {groupIndex}");

        // 预取第一个Group
        Group<TKey, TValue>.PrefetchNextGroup(&_table[groupIndex]);

        for (int i = 0; i < _groupCount; i++) {
            var probeIndex = (groupIndex + i) & (_groupCount - 1);
            
            // 预取下一个Group(如果有)
            if (i + 1 < _groupCount) {
                var nextProbeIndex = (groupIndex + i + 1) & (_groupCount - 1);
                Group<TKey, TValue>.PrefetchNextGroup(&_table[nextProbeIndex]);
            }

            int slot = _table[probeIndex].FindSlot(h2, h1, key, _comparer);
            if (slot >= 0) {
                Console.WriteLine($"在组 {probeIndex} 槽位 {slot} 找到键 {key},执行移除");
                RemoveAt(probeIndex, slot);
                return true;
            }
            
            if (_table[probeIndex].FindEmptyOrTombstone() >= 0)
                break;
        }

        Console.WriteLine($"未找到键 {key},移除失败");
        return false;
    }

    // 移除指定位置的元素
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private void RemoveAt(long groupIndex, int slot) {
        Console.WriteLine($"移除组 {groupIndex} 槽位 {slot}");
        _table[groupIndex].SetMetadata(slot, SwissConfig.Tombstone);
        _count--;
        _tombstones++;
        Console.WriteLine($"移除完成,当前计数: {_count}, 墓碑数: {_tombstones}");

        // 只有在自动缩容模式下才检查是否需要缩容
        if (AutoShrink && ShouldShrink()) {
            Console.WriteLine("自动缩容条件满足,执行缩容");
            Resize();
        }
    }

    // 手动缩容方法
    public void Shrink() {
        Console.WriteLine("手动缩容检查");
        if (ShouldShrink()) {
            Console.WriteLine("手动缩容条件满足,执行缩容");
            Resize();
        }
        else {
            Console.WriteLine("手动缩容条件不满足,跳过");
        }
    }

    // 检查是否需要扩容
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool ShouldExpand() {
        bool should = _count > (int)(_groupCount * SwissConfig.GroupSize * SwissConfig.MaxCap);
        if (should) Console.WriteLine($"扩容检查: 当前计数 {_count} > {_groupCount * SwissConfig.GroupSize * SwissConfig.MaxCap}");
        return should;
    }
    
    // 检查是否需要缩容
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool ShouldShrink() {
        bool should = _count < (int)(_groupCount * SwissConfig.GroupSize * SwissConfig.MinCap);
        if (should) Console.WriteLine($"缩容检查: 当前计数 {_count} < {_groupCount * SwissConfig.GroupSize * SwissConfig.MinCap}");
        return should;
    }

    // 调整哈希表大小
    private void Resize() {
        Console.WriteLine($"开始调整大小,当前组数: {_groupCount}, 元素数: {_count}, 墓碑数: {_tombstones}");
        
        int newGroupCount = ShouldShrink() 
            ? Math.Max(1, _groupCount / 2)
            : _groupCount * 2;
        newGroupCount = (int)BitOperations.RoundUpToPowerOf2((uint)newGroupCount);

        Console.WriteLine($"新组数计算为: {newGroupCount} ({(ShouldShrink() ? "缩容" : "扩容")})");

        var newTable = (Group<TKey, TValue>*)NativeMemory.Alloc(
            (nuint)(newGroupCount * sizeof(Group<TKey, TValue>)), 
            (nuint)16);
        
        if (newTable == null)
            throw new OutOfMemoryException("Failed to allocate memory for resized table");

        try {
            Group<TKey, TValue>* oldTable = _table;
            int oldGroupCount = _groupCount;

            Console.WriteLine("初始化新表的Groups...");
            for (int i = 0; i < newGroupCount; i++) {
                newTable[i] = *Group<TKey, TValue>.Create();
            }
            int migratedCount = 0;

            Console.WriteLine("开始迁移数据...");
            if (oldTable != null) {
                for (int i = 0; i < oldGroupCount; i++) {
                    Console.WriteLine($"迁移组 {i}...");
                    for (int slot = 0; slot < SwissConfig.GroupSize; slot++) {
                        byte meta = oldTable[i].GetMetadata(slot);
                        if (meta == SwissConfig.Empty || meta == SwissConfig.Tombstone)
                            continue;
                        TKey key = oldTable[i].Keys[slot];
                        TValue value = oldTable[i].Values[slot];

                        // 由于可变类型,这里需要重新计算hash
                        var (h1, h2) = GetHashes(key);
                        Console.WriteLine($"迁移键: {key}, h1={h1}, h2={h2}");

                        long newGroupIndex = h1 & (newGroupCount - 1);
                        ref Group<TKey, TValue> newGroup = ref newTable[newGroupIndex];
                            
                        int newSlot = newGroup.FindEmptyOrTombstone();
                        if (newSlot == -1) {
                            throw new InvalidOperationException("Critical error: no empty slot during resize");
                        }
                        Console.WriteLine($"将键 {key} 迁移到新组 {newGroupIndex} 槽位 {newSlot}");
                            
                        // newGroup.H1Array[newSlot] = h1;
                        newGroup.Keys[newSlot] = key;
                        newGroup.Values[newSlot] = value;
                        newGroup.SetMetadata(newSlot, h2);
                        migratedCount++;
                    }
                }

                for (int i = 0; i < oldGroupCount; i++) {
                    oldTable[i].Destroy();
                }
                NativeMemory.Free(oldTable);
                oldTable = null;
                oldGroupCount = 0;
            }

            _table = newTable;
            _groupCount = newGroupCount;
            
            _count = migratedCount;
            _tombstones = 0; // 新表无墓碑
            Console.WriteLine($"调整大小完成,新组数: {_groupCount}, 迁移元素数: {migratedCount}");
        }
        catch (Exception ex) {
            Console.WriteLine($"调整大小过程中发生错误: {ex.Message}");
            // 清理部分初始化的新表
            for (int i = 0; i < newGroupCount; i++) {
                newTable[i].Destroy();
            }
            NativeMemory.Free(newTable);
            newTable = null;
            throw;
        }
    }

    // 清空哈希表
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void Clear() {
        Console.WriteLine("清空哈希表");
        if (_table == null) return;
        for (int i = 0; i < _groupCount; i++) {
            _table[i].Clear();
        }
        _count = 0;
        _tombstones = 0;
        Console.WriteLine("哈希表已清空");
    }

    // 获取枚举器
    public Enumerator GetEnumerator() => new Enumerator(_table, _groupCount);

    IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() 
        => GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    // 枚举器实现
    public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>, IEnumerator {
        private readonly Group<TKey, TValue>* _groups;
        private readonly int _groupCount;
        private int _currentGroup;
        private int _currentSlot;
        private KeyValuePair<TKey, TValue> _current;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal Enumerator(Group<TKey, TValue>* groups, int groupCount) {
            _groups = groups;
            _groupCount = groupCount;
            _currentGroup = 0;
            _currentSlot = -1;
            _current = default;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool MoveNext() {
            while (_currentGroup < _groupCount) {
                ref Group<TKey, TValue> group = ref _groups[_currentGroup];
                
                // 使用局部变量减少数组边界检查
                int slot = _currentSlot + 1;
                while (slot < SwissConfig.GroupSize) {
                    byte meta = group.GetMetadata(slot);
                    if (meta != SwissConfig.Empty && meta != SwissConfig.Tombstone) {
                        _currentSlot = slot;
                        _current = new KeyValuePair<TKey, TValue>(
                            group.Keys[slot],
                            group.Values[slot]);
                        return true;
                    }
                    slot++;
                }
                
                _currentGroup++;
                _currentSlot = -1;
            }

            _current = default;
            return false;
        }

        public readonly KeyValuePair<TKey, TValue> Current => _current;
        
        // 提供ref返回版本以支持高性能场景        
        public ref readonly KeyValuePair<TKey, TValue> CurrentRef {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get {
                // 使用局部变量避免直接返回字段引用
                ref var localRef = ref _current;
                return ref localRef;
            }
        }

        readonly object IEnumerator.Current => Current;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public readonly void Dispose() { }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Reset() {
            _currentGroup = 0;
            _currentSlot = -1;
            _current = default;
        }

        // 这里可以实现一个双调排序,因为具备2n性质,所以输出一个SortArray

    }
}

public class Program {
    public static void Main() {
        // 测试自动缩容模式
        // TestAutoShrinkMode();
        
        // 测试手动缩容模式
        // TestManualShrinkMode();
        
        // 测试基本功能
        TestBasicFunctionality();
    }

    // 测试自动缩容模式
    public static void TestAutoShrinkMode() {
        Console.WriteLine("\n=== 测试自动缩容模式 ===");

        var map = new SwissTable<int, string>();
        map.AutoShrink = true;

        // 添加元素
        for (int i = 1; i <= 20; i++) {
            map.TryAdd(i, $"Value_{i}");
        }
        Console.WriteLine($"添加后元素数量: {map.Count}");
        
        // 移除元素(会自动触发缩容)
        for (int i = 1; i <= 15; i++) {
            map.Remove(i);
        }
        Console.WriteLine($"移除后元素数量: {map.Count}");
    }

    // 测试手动缩容模式
    public static void TestManualShrinkMode() {
        Console.WriteLine("\n=== 测试手动缩容模式 ===");

        var map = new SwissTable<int, string>();
        map.AutoShrink = false;
        // 添加元素
        for (int i = 1; i <= 20; i++) {
            map.TryAdd(i, $"Value_{i}");
        }
        Console.WriteLine($"添加后元素数量: {map.Count}");
        
        // 移除元素(不会自动缩容)
        for (int i = 1; i <= 15; i++) {
            map.Remove(i);
        }
        Console.WriteLine($"移除后元素数量: {map.Count} (未缩容)");
        
        // 手动缩容
        map.Shrink();
        Console.WriteLine($"手动缩容后元素数量: {map.Count}");
    }

    // 测试基本功能
    public static void TestBasicFunctionality() {
        Console.WriteLine("\n=== 测试int类型键及扩容后查找 ===");
        var intMap = new SwissTable<int, string>();

        int num = 0xFF;
        if (!intMap.TryAdd(num, $"Value_{num}")) {
            Console.WriteLine($"添加键{num}失败!");
        }

        // 添加足够多的元素以触发多次扩容
        int targetCount = 20;
        for (int i = 0; i <= targetCount; i++) {
            if (!intMap.TryAdd(i, $"Value_{i}")) {
                Console.WriteLine($"添加键{i}失败!");
                break;
            }
        }

        Console.WriteLine($"\n=== 最终验证所有键 ===");

        bool allFound = true;
        for (int i = 0; i <= targetCount; i++) {
            if (!intMap.TryGetValue(i, out _)) {
                Console.WriteLine($"最终验证失败!键{i}未找到");
                allFound = false;
                break;
            }
        }
        Console.WriteLine(allFound ? "所有键在扩容后都能正确找到" : "存在扩容后丢失的键");
        
        Console.WriteLine("\n=== 遍历所有元素 ===");
        foreach(var item in intMap) {
            Console.WriteLine($"{item}");
        }

        Console.WriteLine("\n测试完成!");
    }
}
posted @ 2025-06-29 02:41  惊惊  阅读(35)  评论(0)    收藏  举报