根据同时已知的问题细节,这是对该问题的另一种看法。本文基于以下假设:
- 记录数约为 1.9 亿,已修复
- 密钥是 64 字节的哈希值,例如 SHA-256
- 值是文件名:可变长度,但合理(平均长度
- 页面大小 4 KiByte
数据库中文件名的有效表示是一个不同的主题,此处无法解决。如果文件名很尴尬 - 平均较长和/或 Unicode - 那么哈希解决方案将通过增加磁盘读取计数(更多溢出,更多链接)或减少平均占用率(更多浪费空间)来惩罚您。不过,B-tree 解决方案的反应稍微温和一些,因为在任何情况下都可以构建出最优树。
在这种情况下,最有效的解决方案 - 并且在很大程度上实现最简单 - 是散列,因为您的密钥已经是完美的散列。取散列的前 23 位作为页码,页面布局如下:
page header
uint32_t next_page
uint16_t key_count
key/offset vector
uint16_t value_offset;
byte key[64];
... unallocated space ...
last arrived filename
...
2nd arrived filename
1st arrived filename
值(文件名)从页面末尾向下存储,前缀为 16 位长度,键/偏移向量向上增长。这样,低/高键计数和短/长值都不会导致不必要的空间浪费,就像固定大小的结构一样。您也不必在关键字搜索期间解析可变长度结构。除此之外,我的目标是尽可能简单 - 没有过早的优化。堆的底部可以存储在页眉中,KO.[PH.key_count].value_offset(我的偏好),或者计算为KO.Take(PH.key_count).Select(r => r.value_offset).Min(),无论你最喜欢什么。
键/偏移向量需要在键上保持排序,以便您可以使用二进制搜索,但值可以在它们到达时写入,它们不需要按任何特定顺序。如果页面溢出,则在文件的当前末尾分配一个新的,就像它一样(将文件增加一页)并将其页码存储在适当的标题槽中。这意味着您可以在一个页面内进行二分搜索,但所有链接的页面都需要一个一个地读取和搜索。此外,您不需要任何类型的文件头,因为文件大小是可用的,这是唯一需要维护的全局管理信息。
将文件创建为稀疏文件,其页数由您选择的散列密钥位数指示(例如 8388608 页 23 位)。稀疏文件中的空页面不占用任何磁盘空间并读取为全 0,这与我们的页面布局/语义完美配合。每当您需要分配溢出页时,将文件扩展一页。注意:这里的“稀疏文件”并不是很重要,因为当您完成构建文件时,几乎所有页面都已写入。
为了最大限度地提高效率,您需要对数据进行一些分析。在我的模拟中 - 使用随机数作为散列的替代品,并假设平均文件名大小为 62 字节或更小 - 最佳结果是制作 2^23 = 8388608 个桶/页。这意味着您将散列的前 23 位作为要加载的页码。以下是详细信息:
# bucket statistics for K = 23 and N = 190000000 ... 7336,5 ms
average occupancy 22,6 records
0 empty buckets (min: 3 records)
310101/8388608 buckets with 32+ records (3,7%)
这样可以将链接保持在最低限度,平均而言,每次搜索您只需阅读 1.04 页。将哈希键大小增加一位到 24 会将预期的溢出页面数减少到 3,但文件大小会增加一倍,并将平均占用率降低到每页/存储桶 11.3 条记录。将密钥减少到 22 位意味着几乎所有页面 (98.4%) 都会溢出 - 这意味着文件的大小实际上与 23 位相同,但每次搜索必须执行两倍的磁盘读取。
因此,您会看到对数据进行详细分析以确定用于哈希寻址的正确位数是多么重要。您应该运行使用实际文件名大小并跟踪每页开销的分析,以查看 22 位到 24 位的实际图片是什么样的。这需要一段时间才能运行,但这仍然比盲目地构建数 GB 文件然后发现您浪费了 70% 的空间或搜索平均需要显着超过 1.05 次页面读取要快得多。
任何基于 B-tree 的解决方案都将涉及更多(阅读:复杂),但由于显而易见的原因,并且即使仅假设有足够数量的内部节点,也无法将每次搜索的页面读取计数减少到 1.000 以下可以缓存在内存中。如果您的系统拥有如此庞大的 RAM,可以在很大程度上缓存数据页面,那么哈希解决方案将与基于某种 B 树的解决方案一样受益。
尽管我想找个借口来构建一个非常快速的混合基数/B+树,但散列解决方案只需一小部分工作量即可提供基本相同的性能。在这里,B-treeish 解决方案可以超越散列的唯一一点是空间效率,因为为现有 预排序 数据构建最佳树是微不足道的。