C# 支持前缀搜索的排序文本内存结构的空间效率

C# 支持前缀搜索的排序文本内存结构的空间效率,c#,.net,algorithm,prefix,trie,C#,.net,Algorithm,Prefix,Trie,我有一个问题:我需要基于文件路径前缀高效查找文件系统数据。换句话说,排序文本的前缀搜索。你说,使用trie,我也这么想。问题是,尝试空间效率不够,没有其他技巧也不行 我有大量的数据: 磁盘上以明文Unix格式列出的大约450M 大约800万行 gzip默认压缩到31米 bzip2默认值压缩到21M 我不想在记忆中接近450米的地方吃饭。在这一点上,我很乐意使用100米左右的位置,因为前缀的形式有很多冗余 我使用C#来完成这项工作,而trie的直接实现仍然需要文件中每行有一个叶节点。考虑到每个

我有一个问题:我需要基于文件路径前缀高效查找文件系统数据。换句话说,排序文本的前缀搜索。你说,使用trie,我也这么想。问题是,尝试空间效率不够,没有其他技巧也不行

我有大量的数据:

  • 磁盘上以明文Unix格式列出的大约450M
  • 大约800万行
  • gzip默认压缩到31米
  • bzip2默认值压缩到21M
我不想在记忆中接近450米的地方吃饭。在这一点上,我很乐意使用100米左右的位置,因为前缀的形式有很多冗余

我使用C#来完成这项工作,而trie的直接实现仍然需要文件中每行有一个叶节点。考虑到每个叶节点都需要对最终文本块进行某种引用(32位,比如一个字符串数据数组的索引,以最大限度地减少字符串重复),并且CLR对象开销为8字节(使用windbg/SOS进行验证),我将在结构开销上花费>96000000字节,而根本没有文本存储

让我们看看数据的一些统计属性。当填充到trie中时:

  • 文本的唯一“块”总数约为110万
  • 在一个文本文件中,磁盘上的唯一块总数约为16M
  • 平均块长度为5.5个字符,最大值为136
  • 如果不考虑重复,块中总共约有5200万个字符
  • 内部trie节点平均约6.5个子节点,最大值为44
  • 大约1.8M的内部节点
叶创建的超额率约为15%,内部节点的超额创建率为22%——即超额创建,我指的是在trie构建期间创建的叶和内部节点,但不是在最终trie中,作为每种类型的最终节点数的比例

下面是来自SOS的堆分析,指出了使用最多内存的位置:

 [MT    ]--[Count]----[   Size]-[Class                                          ]
 03563150       11         1584 System.Collections.Hashtable+bucket[]
 03561630       24         4636 System.Char[]
 03563470        8         6000 System.Byte[]
 00193558      425        74788      Free
 00984ac8    14457       462624 MiniList`1+<GetEnumerator>d__0[[StringTrie+Node]]
 03562b9c        6     11573372 System.Int32[]
*009835a0  1456066     23297056 StringTrie+InteriorNode
 035576dc        1     46292000 Dictionary`2+Entry[[String],[Int32]][]
*035341d0  1456085     69730164 System.Object[]
*03560a00  1747257     80435032 System.String
*00983a54  8052746     96632952 StringTrie+LeafNode
这种结构将数据量降低到139M,并且对于只读操作来说仍然是一种高效的可遍历trie。由于它非常简单,我可以轻松地将其保存到磁盘并进行恢复,以避免每次重新创建trie的成本


那么,有没有比trie更有效的前缀搜索结构的建议?我应该考虑的另一种方法是:

< P>墙上的想法:而不是一个The哈希表。内存中只有散列和字符串数据,可能是压缩的


或者你能负担得起一页的阅读费吗?只有散列和文件在内存中的位置,才能检索具有与该散列匹配的行的“页面”,可能是少量有序行,因此在发生冲突时可以非常快速地进行搜索

因为只有110万个块,所以可以使用24位而不是32位索引块,并在那里节省空间


您还可以压缩块。也许这是个不错的选择。我还将尝试以下策略:不使用字符作为符号进行编码,而应该对字符转换进行编码。因此,不要看角色出现的概率,而要看状态为当前角色时的转换概率。

你可以找到一篇与你的问题相关的科学论文(引用作者:“实验表明,我们的索引支持在空间占用率接近通过gzip、bzip或ppmdi压缩字符串字典所能实现的空间占用率的情况下进行快速查询。”-但不幸的是,这篇论文仅用于支付。)我不确定这些想法实现起来有多困难。本文作者有一个网站,你可以在这里找到各种压缩索引算法的实现(在“索引集合”下)


如果您想继续您的方法,请务必查看有关和的网站。

您将如何利用这些数据?大量处理或只是几次查找;您能否提供一些想法,说明高效存储和处理之间的折衷是可以接受的?基本上是缓存文件系统查找操作,以便在物理磁盘上,无需查询诸如获取目录中的所有文件、递归地获取目录中的所有文件等内容,而无需查询磁盘,磁盘始终不在内存中,事实上是跨网络的=>往返次数过多。性能预期是执行150次前缀查找(即查找具有此前缀的所有行)平均返回100行不应该超过100毫秒。事实上,我的
SlimTrie
方法从磁盘加载并列出8000000行需要10秒=>~18ms。这是在关闭优化的情况下,打开优化的情况下,需要8.5秒-这包括应用程序启动。140M还不算太坏,但考虑到数据中的冗余,我认为e这是可以改进的。在我看到trie中的块之后,我写的第一件事是Huffman树-我想尝试将行编码为位字符串,每个块一个字符串,连接起来-但是当我写位打包逻辑时,我想改为使用平面值类型数组进行trie编码。实现Huffman encod正确、高效地编索引,特别是解码,很快就会变得非常乏味。我可能会重新选择它,也许会根据字符频率进行编码。是的,我一直在考虑使用少于32位的索引。其他一些事情:16M字符数据接近24位,但如果我将字符数据与word bo对齐Dadaries平均每个数据块花费0.5字节,我可以使用24位来索引32M位置,节省了一半。我为哈夫曼树编码编写的位打包逻辑可能对使用少于整数字节来存储索引很有用。我的下一步可能是编写“位域数组”“同学们,我会奖励这一个,我写了一点
class SlimTrie
{
    byte[] _stringData; // UTF8-encoded, 7-bit-encoded-length prefixed string data

    // indexed by _interiorChildIndex[n].._interiorChildIndex[n]+_interiorChildCount[n]
    // Indexes interior_node_index if negative (bitwise complement),
    // leaf_node_group if positive.
    int[] _interiorChildren;

    // The interior_node_index group - all arrays use same index.
    byte[] _interiorChildCount;
    int[] _interiorChildIndex; // indexes _interiorChildren
    int[] _interiorChunk; // indexes _stringData

    // The leaf_node_index group.
    int[] _leafNodes; // indexes _stringData

    // ...
}