C# 如何可靠地测试/基准测试.Net哈希集的大小(包括空桶)<;T>;对象

C# 如何可靠地测试/基准测试.Net哈希集的大小(包括空桶)<;T>;对象,c#,.net,hashtable,hashset,C#,.net,Hashtable,Hashset,作为个人教育和实验的练习,我想创建自己的哈希表类。具体地说,我想编写这个对象,除了为了测试目的映射到现有接口之外,不使用任何现有代码(即,这个对象不会从另一个类继承) 因为我计划用C#编写这个,所以我的“基准”将是.NetHashSet类。我可以轻松地测试添加、删除和查找请求的执行时间,但我不知道如何测试HashSet基准对象的大小,包括未来添加请求的所有空桶 如何跟踪HashSet对象动态增长以为将来的插入腾出空间时的大小 为了清楚起见,我不需要知道确切的字节数(我知道.Net framewo

作为个人教育和实验的练习,我想创建自己的
哈希表
类。具体地说,我想编写这个对象,除了为了测试目的映射到现有接口之外,不使用任何现有代码(即,这个对象不会从另一个类继承)

因为我计划用C#编写这个,所以我的“基准”将是.Net
HashSet
类。我可以轻松地测试添加、删除和查找请求的执行时间,但我不知道如何测试
HashSet
基准对象的大小,包括未来添加请求的所有空桶

如何跟踪
HashSet
对象动态增长以为将来的插入腾出空间时的大小


为了清楚起见,我不需要知道确切的字节数(我知道.Net framework使获得许多类型对象的确切大小变得有点困难),但我更希望知道有多少存储桶正在使用,有多少是空的,等待使用,当我执行各种类型的测试时。

这是一个有趣的问题强文本。。。我有一个激进的建议:

在初始化哈希集之前,启动应用程序并获取内存大小。您可以使用Process.GetCurrentProcess().WorkingSet64(在msdn上:)来执行此操作


然后再次填充HashSet和print Process.GetCurrentProcess().WorkingSet64。不同之处在于您寻求的大小。

我不太熟悉
HashSet
的内部结构,但您可以使用反射来获取其内部值:

HashSet<int> hashSet = new HashSet<int>();
var countField = typeof(HashSet<int>).GetField("m_count", BindingFlags.NonPublic | BindingFlags.Instance);
var freeListField = typeof(HashSet<int>).GetField("m_freeList", BindingFlags.NonPublic | BindingFlags.Instance);
var count = countField.GetValue(hashSet);
var freeList = freeListField.GetValue(hashSet);
HashSet HashSet=newhashset();
var countField=typeof(HashSet).GetField(“m_count”,BindingFlags.NonPublic | BindingFlags.Instance);
var freeListField=typeof(HashSet).GetField(“m_freeList”,BindingFlags.NonPublic | BindingFlags.Instance);
var count=countField.GetValue(hashSet);
var freeList=freeListField.GetValue(hashSet);

注意:这种违反私有成员访问权限的行为当然非常丑陋,但我相信在您的开发/测试阶段是可以接受的。

获取存储桶数量和大小的最佳方法是使用反射。唯一的问题是,您需要首先了解集合的行为。在读了一点代码并做了一些尝试和错误之后,似乎需要计算私有
m_bucket
数组的大小以获得bucket的数量,并计算大于0的值的数量以获得已使用的bucket的数量。方法如下所示:

static void CountBuckets<T>(HashSet<T> hashSet)
{
    var field = typeof(HashSet<T>).GetField("m_buckets", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);

    var buckets = (int[])field.GetValue(hashSet);

    int numberOfBuckets = 0;
    int numberOfBucketsUsed = 0;

    if (buckets != null)
    {
        numberOfBuckets = buckets.Length;
        numberOfBucketsUsed = buckets.Where(i => i != 0).Count();
    }

    Console.WriteLine("Number of buckets: {0} / Used: {1}", numberOfBuckets, numberOfBucketsUsed);
}
在那里,我做了一些测试:

    var hashSet = new HashSet<Hash>();

    CountBuckets(hashSet);
    // Number of buckets: 0 / Used: 0

    var firstHash = new Hash(0);

    hashSet.Add(firstHash);

    CountBuckets(hashSet);
    // Number of buckets: 3 / Used: 1

    hashSet.Add(new Hash(1));
    hashSet.Add(new Hash(2));

    CountBuckets(hashSet);
    // Number of buckets: 3 / Used: 3

    hashSet.Add(new Hash(3));

    CountBuckets(hashSet);
    // Number of buckets: 7 / Used: 4

    hashSet.Add(new Hash(1));

    CountBuckets(hashSet);
    // Number of buckets: 7 / Used: 4

    hashSet.Remove(firstHash);

    CountBuckets(hashSet);
    // Number of buckets: 7 / Used: 3
var hashSet=new hashSet();
countbucket(hashSet);
//桶数:0/已使用:0
var firstHash=新哈希(0);
Add(firstHash);
countbucket(hashSet);
//铲斗数量:3/使用:1
Add(新哈希(1));
Add(新哈希(2));
countbucket(hashSet);
//铲斗数量:3个/使用:3个
Add(新哈希(3));
countbucket(hashSet);
//铲斗数量:7/使用:4
Add(新哈希(1));
countbucket(hashSet);
//铲斗数量:7/使用:4
移除(firstHash);
countbucket(hashSet);
//铲斗数量:7/使用:3

这听起来与直觉行为一致。首先,桶的数量是0。添加元素后,它将扩展为3。桶的数量保持稳定,直到添加第四个元素,将计数扩展到7。在模拟散列冲突时,使用的存储桶数保持稳定,这与预期的一样。删除一个元素会减少使用的bucket的数量。

@BartoszKP我明确表示,我对字节大小不感兴趣。我想知道有多少HashSet bucket存在,无论是否在使用中。差别很大。如果我能做到这一点的唯一方法是获取字节大小并进行计算,那么这就足够了。但是,我希望有一种方法可以检查HashSet对象并获取此信息,而无需进行字节大小的计算。好的,我调整了措辞以澄清问题。对不起,我更改了措辞。我把大胆的评论放在最后,因为我想避免“网络效应”。我不想让较低级别的mods略过我的问题,只是简单地同意,而没有抓住我的最后评论。你能详细说明为什么使用的桶的数量对你来说是一个有意义的数字吗?我希望这个类的用户最多对两件事感兴趣:速度和内存使用。你可以轻松测量的速度。您还可以测量内存使用,但不能通过计算存储桶的数量来测量:您自己的类的存储桶也很可能更大。我本来希望所需内存总量(以字节为单位)是一个更有意义的统计数据,但我的预期似乎与您的不符。为什么不使用Reflector提取现有.NET哈希集的代码,然后对其进行修改,以公开有关大小、桶使用情况和类似信息?。。当然,您只会将其用于测试目的。出于几个原因,这是不可靠的。首先,您永远不知道GC何时清理已处理的数据。应用程序的大小可以独立于HashSet(或任何集合类)的使用而增长和收缩。MaxWorkingSet将为您提供达到的最大值。。这将忽略您提到的所有GC清理工作集,但您的链接指向私有内存大小,这与工作集不同。工作集是物理内存,它只是私有内存的一个子集……我知道
HashSet
对象的大小是动态增长的,但我怀疑他们是否会将类型设计为在对象删除时收缩。如果.Net有完全动态大小的HashSet,那就太好了,但在某些情况下,我可以看到它可能没有那么有用
    var hashSet = new HashSet<Hash>();

    CountBuckets(hashSet);
    // Number of buckets: 0 / Used: 0

    var firstHash = new Hash(0);

    hashSet.Add(firstHash);

    CountBuckets(hashSet);
    // Number of buckets: 3 / Used: 1

    hashSet.Add(new Hash(1));
    hashSet.Add(new Hash(2));

    CountBuckets(hashSet);
    // Number of buckets: 3 / Used: 3

    hashSet.Add(new Hash(3));

    CountBuckets(hashSet);
    // Number of buckets: 7 / Used: 4

    hashSet.Add(new Hash(1));

    CountBuckets(hashSet);
    // Number of buckets: 7 / Used: 4

    hashSet.Remove(firstHash);

    CountBuckets(hashSet);
    // Number of buckets: 7 / Used: 3