不可变集合哈希码

本文关键字:哈希码 集合 不可变 | 更新日期: 2023-09-27 18:11:59

我的对象将有一个字节数组,它可能有数千个元素。该数组将在构造期间设置,然后永远不会更改。我需要能够比较来自两个不同对象的数组,看看它们是否完全相同。

我知道可以使用Enumerable。SequenceEqual来比较两个值,但这有一个我想避免的开销。

我的计划是在生成集合并存储用于比较的哈希值之后,为Foo对象的列表使用类似于Good GetHashCode()的重写。

我想知道是否有一个不可变的集合类型构建到c#或。net已经做到了这一点,或者如果有一个更好的选择,我忽略了。

不可变集合哈希码

我把几个不同的方法放在一起比较字节数组,我使用了一个任意长度为10000的数组,并假设两个比较的数组长度相同(因为"宽相位"长度检查显然不是很有趣:))

也许你可以把它作为在比较数组是否相等时决定使用哪种方法的基础。

结果是三种场景(相等,第一个元素不同,最后一个元素不同)的5次迭代的平均值,计时单位为ms。

---------------
Identical elements
---------------
SequenceEqual: 5.98142
BasicEqual: 0.11864
UnsafeMemCmp: 0.15542
SafeMemCmp: 0.12896
---------------
First element different
---------------
SequenceEqual: 0.00056
BasicEqual: 0.00012
UnsafeMemCmp: 0.0002
SafeMemCmp: 0.00182
---------------
Last element different
---------------
SequenceEqual: 0.14942
BasicEqual: 0.03178
UnsafeMemCmp: 0.0015
SafeMemCmp: 0.00326
---------------

我选择的4种方法是:

SequentalEqual

static bool SequenceEqual(byte[] arr1, byte[] arr2)
{
    return arr1.SequenceEqual(arr2);
}

BasicEqual

static bool BasicEqual(byte[] arr1, byte[] arr2)
{
    for (var i = 0; i < 10000; i++)
        if (arr1[i] != arr2[i])
            return false;
     return true;
}

UnsafeMemCmp

[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe int memcmp(byte* b1, byte* b2, int count);
static unsafe bool UnsafeMemCmp(byte[] arr1, byte[] arr2)
{
    fixed (byte* b1 = arr1, b2 = arr2)
    {
        return memcmp(b1, b2, 10000) == 0;
    }
}

SafeMemCmp

[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int memcmp(IntPtr b1, IntPtr b2, int count);
static bool SafeMemCmp(byte[] arr1, byte[] arr2)
{
    var a = Marshal.AllocHGlobal(arr1.Length);
    var b = Marshal.AllocHGlobal(arr2.Length);
    try
    {        
        Marshal.Copy(arr1, 0, a, arr1.Length);
        Marshal.Copy(arr2, 0, b, arr2.Length);
        return memcmp(a, b, 10000) == 0;
    }
    finally
    {
        Marshal.FreeHGlobal(a);
        Marshal.FreeHGlobal(b);
    }
}

为了完成,使用以下方法运行测试:

static void RunTest(string name, Func<byte[], byte[], bool> action, byte[] a, byte[] b)
{
    TimeSpan total = TimeSpan.Zero;
    for (var i = 0; i < 5; i++)
    {
        _stopwatch.Reset();
        _stopwatch.Start();
        action(a, b);
        _stopwatch.Stop();
        total += _stopwatch.Elapsed;
    }
    Console.WriteLine(name + ": " + (total.TotalMilliseconds / 5));
}