概述
HashSet 每个插槽有 12 个字节的开销(可以包含一个项目或为空)。这个开销比存储 long 的情况下的数据大小大 150%。
HashSet 还为新数据保留空槽,并且示例中的项目数(每个 HashSet 约 1250 万个项目)仅由于空槽而导致内存使用量增加约 66%。
如果您需要 O(1) 确认集合中的存在,那么 HashSet 可能是您能做的最好的。如果您知道您的数据有什么特别之处(例如,它包含连续“运行”数百个项目),那么您可能会想出一种更聪明的方式来表示这种需要更少内存的方式。在不了解更多数据的情况下,很难就此提出建议。
测试程序
static void Main(string[] args)
{
var q = new Queue<long>();
var hs = new []
{
new HashSet<long>(),
new HashSet<long>(),
new HashSet<long>(),
new HashSet<long>()
};
for (long i = 0; i < 25000000; ++i)
{
q.Enqueue(i);
if (i < 12500000)
{
foreach (var h in hs)
{
h.Add(i);
}
}
}
Console.WriteLine("Press [enter] to exit");
Console.ReadLine();
}
HashSet 实现 - 单声道
插槽分配策略 - 每次分配时将表的大小加倍。
https://github.com/mono/mono/blob/master/mcs/class/System.Core/System.Collections.Generic/HashSet.cs
HashSet 实现 - MSFT
插槽分配策略 - 使用素数进行分配。这可能会导致大量空白空间,但会减少必须重新分配和重新散列表的次数。
http://referencesource.microsoft.com/#System.Core/System/Collections/Generic/HashSet.cs
内存使用 - 一般大小调整 - 单声道实现
- 初始大小:10 个插槽
- 填充系数:90% 完全触发调整大小
- 调整大小因子:达到填充因子时大小翻倍
内存使用 - 每个插槽 - 两种实现
- 表:每个插槽 1 x 4 字节 int = 4 字节/插槽
- 链接:每个插槽 2 x 4 字节整数 = 8 字节/插槽
- 槽:1 x (sizeof T) 字节 = 8 字节/槽(对于 T = long)
- 总计:20 字节/槽
示例中使用的插槽 - 单声道
该示例在每个 HashSet 中有 1250 万个项目。
slots = 10 * 2 ^ 上限(log2(items / 10))
log2(12,500,000 /10) ~= 20.5
插槽 ~= 2100 万
示例中使用的内存 - 计算 - 单声道
队列:2500 万长 * 8 字节 / 长 = 200 MB
每个 HashSet:2100 万个插槽 * 20 字节/插槽 = 420 MB
所有哈希集:1.68 GB
总计:1.88 GB(+ 大对象堆中的空白空间)
示例中使用的内存 - 使用 Son of Strike 观察 - MSFT 实现
.Net 堆中的 3.5 GB 内存
400 MB 的 Int32 数组(由 HashSet 使用,不用于我们的数据存储)
2.5 GB 的 HashSet Slot 对象
注意:MSFT 的 Slot 对象是 8 个字节加上数据的大小(在本例中为 8 个字节),总共 16 个字节。 2.5 GB 的 Slot 对象是 1.56 亿个 Slot,仅用于存储 5000 万个项目。
dumpheap -stat
!dumpheap -stat
Statistics:
MT Count TotalSize Class Name
00007ffb549af228 1 24 System.Collections.Generic.GenericEqualityComparer`1[[System.Int64, mscorlib]]
[snip]
00007ffb53e80bd8 159 6926 System.String
00007ffb53e81250 27 36360 System.Object[]
00000042ed0a8a30 22 48276686 Free
00007ffb53f066f0 3 402653256 System.Int64[]
00007ffb53e83768 14 431963036 System.Int32[]
00007ffaf5e17e88 5 2591773968 System.Collections.Generic.HashSet`1+Slot[[System.Int64, mscorlib]][]
Total 343 objects
eeheap -gc
!eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x00000042800472f8
generation 1 starts at 0x0000004280001018
generation 2 starts at 0x0000004280001000
ephemeral segment allocation context: none
segment begin allocated size
0000004280000000 0000004280001000 000000428004b310 0x4a310(303888)
Large object heap starts at 0x0000004290001000
segment begin allocated size
0000004290000000 0000004290001000 0000004290009728 0x8728(34600)
00000042dc000000 00000042dc001000 00000042e7717e70 0xb716e70(191983216)
000000433e6e0000 000000433e6e1000 000000434f9835b0 0x112a25b0(287974832)
00000043526e0000 00000043526e1000 000000435a6e1038 0x8000038(134217784)
000000435e6e0000 000000435e6e1000 0000004380c25c00 0x22544c00(575949824)
00000043826e0000 00000043826e1000 000000438826c788 0x5b8b788(95991688)
000000438a6e0000 000000438a6e1000 00000043acc25c00 0x22544c00(575949824)
00000043ae6e0000 00000043ae6e1000 00000043b426c788 0x5b8b788(95991688)
00000043b66e0000 00000043b66e1000 00000043d8c25c00 0x22544c00(575949824)
00000043da6e0000 00000043da6e1000 00000043e026c788 0x5b8b788(95991688)
00000043e26e0000 00000043e26e1000 0000004404c25c00 0x22544c00(575949824)
0000004298000000 0000004298001000 00000042a8001038 0x10000038(268435512)
Total Size: Size: 0xcf1c1560 (3474724192) bytes.
------------------------------
GC Heap Size: Size: 0xcf1c1560 (3474724192) bytes.