“这有多难?”
这是两周前我告诉自己的。 我对 我经常去的Doxygen股票搜索的反应和实用性 直接转到谷歌。 有了这样的表现,对于新的主题,我想要一个 解决方案:
是 快速的 且稳定,无需重新订购结果
是 足够小 不需要任何复杂的服务端解决方案
是先显示相关结果,所以我不需要滚动浏览 找到我要找的东西的无结尾列表
我们能够进行API发现吗( 即时消息 取而代之的looka-head自动合成 仅进行全文搜索)
有一个用户体验,它允许我在页面的任何地方弹出它,导航 只使用键盘,然后返回页面而不丢失内容
甚至不到500行纯JavaS脚本和大约半兆字节的二进制文件 包含搜索数据,我可以在所有7500个Magnum符号中搜索 打字速度比我快。
这个 尝试 搜索结构似乎很适合 lookahead autopletion的案例,如何构建一个巨大的树结构 JavaS脚本并没有真正实现我对更高性能的期望。 第一次命中 当搜索“javasscript fast tries”时 John Resig的这篇博文 (jQuery的助手)。 我很兴奋,甚至 快速的 直到我看到 article写于2011年,在asm.js之前的时代,输入ar-ray和 脚本。
呸,好吧。 我决定从搜索查询中删除“javas script”,并添加一个 关于 拥挤的试穿 最终在结果中弹出。 这颗星星看起来像是正确的方向 继续。
基本思想是:在 m.css Doxygen主题 , 将输入的搜索数据预处理为一个简单的搜索结构 内特德体育 字典
s.然后,在完全弹出后,将尝试 二进制文件。 二进制文件稍后将使用JavaS脚本加载到 无需构建任何新结构,直接在其上进行浏览器和搜索 这样可以最小化初始启动时间和内存使用。
trie中的每个节点都将存储一个结果列表(用简单的 集成到外部阵列、外部计划)和地图 字符到子节点。 我设定了64000个符号的合理上限, 二进制文件最大大小为256 MB,最大分辨率为256,最多256个子8位 每个节点的字符。 在此基础上 trie大致如下:
字段大小
内容
32亿
根节点偏移
…
…
8b个
节点N结果计数
第页
8b个
节点N子计数
c(c)
r次16b
节点结果
8b+24b
节点N子1字符+字节偏移
8b+24b
节点N子2字符+字节偏移
…
…
8b+24b
节点N子节点
c(c)
字符+字节偏移
…
…
trie在提供服务时将精通post或der,这意味着 文件中的每个节点偏移在序列化其部分时都是已知的。 这个 唯一的例外是根节点偏移,它在 文件作为最后一步。
现在,哪些内容将被保存到试卷中? 让我们从一个非常 简化案例: Magnum公司 和 杂志::数学 名称空间, 杂志::数学::矢量 和 Magnum::数学::范围 课程和 三个功能 磁::数学::矢量::min() , 磁数::数学::范围::min() 和 磁数::数学::min() .股票 Doxygen搜索允许我只搜索我们的叶子名称-如果我想的话 仰望 数学 :: 最小值 ()
,正在进行 最小值 ()
结果太多,我 需要手动过滤结果,这是对大脑材料的浪费。 目标 这里可以搜索我们 任何 预修复,这意味着每个符号 必须保存到trie中,并附带所有可能的修复程序。
良好的缺陷可视化是理解数据的关键。 元组 包含上述七个符号,并使用不同的修复程序看起来像 这-每列是一个树深度级别(转到下一列 搜索正确的Letter)和parlular叶节点包含结果ID (以cyan表示)。 搜索是区分大小写的,因此所有字符串都不会被错误化 小写:
镁铝合金 [ 0 ]
||| ::数学 [ 1 ]
||| ::矢量 [ 2 ]
|||| ::分钟 [ 三 ]
||| 范围 [ 4 ]
||| | ::分钟 [ 5 ]
||| 最小值 [ 6 ]
|| 第个 [ 1 ]
||| ::矢量 [ 2 ]
||| | ::分钟 [ 三 ]
||| 范围 [ 4 ]
||| | ::分钟 [ 5 ]
||| 最小() [ 6 ]
| 在里面 [ 三 , 5 , 6 ]
矢量 [ 2 ]
| ::分钟() [ 三 ]
范围 [ 4 ]
| ::分钟() [ 5 ]
上述方法有一个问题——所有符号都是 根据深度,在试验中保持两次或两次以上。 使用 数千个符号这将是一个显著的增长。 多亏了 由于视觉上的缺陷,解决方案非常直观-如果我愿意怎么办 只需删除那些 看起来一样 ?
在序列化过程中,树以post-order转换,这意味着 节点的最终二进制形式是在对其部件进行序列化时已知的。 那就是 足以维护一个系列化子树的查找表。 当一个新的系列化 子树已经在查找表中,它不是写入文件,而是写入 使用已经存在的实例的字节偏移集代替。 当然是这样 要求具有相同数据的子树序列相同,独立 为了实现这一点,我必须切换到他们在文件中的位置 从累赘子偏移(如文章中建议的节省空间)到 绝对偏移。
与上述视觉效果相一致, #
注释,其中a 这种方法消除了褶皱子树:
镁铝合金 [ 0 ]
||| ::数学 [ 1 ]
||| ::矢量 [ 2 ]
||| | ::分钟 [ 三 ]
||| 范围 [ 4 ]
||| | ::分钟 [ 5 ]
||| 最小值 [ 6 ]
|| t吨 #
| 在里面 [ 三 , 5 , 6 ]
v(v) #
第页 #
随着子树合并的到位,这个最小案例的系列化大小 从592字节降到了不到一半:290字节。 在完整的Magnum中 搜索数据,这节省了半兆字节。 这是一个很好的方法 它完全是语言的-它只在垃圾箱上工作 数据。
到目前为止,只有一次试验获得了如下结果 [ 三 , 5 , 6 ]
.那是 显然不足以向用户显示搜索结果的链接。 旁边的 必须有一个从这些信息到实际结果标题和URL的地图。
每个标题和URL都是一个不同长度和顺序的字符串,用于 查找一个即时操作,首先需要一个表映射 索引与字符串数据的字节偏移。 因为我正在检索数据 只有24位用于字节偏移,我可以在这里做同样的事情,并使用resing 8 用于各种广告形式的位,例如symbol类型。
字段大小
内容
8b+24b
Result 0标志+字节偏移
o_0(零)
8b+24b
Result 1标志+字节偏移
臭氧
…
…
8b+24b
Result N标志+字节偏移
臭氧
32亿
文件大小
我
o_1至o_0
结果0标题, '\0'
,URL
…
…
左-右
Result N标题, '\0'
,网址
查找结果
第页
从字节偏移范围读取数据 由索引提供
第页
和
r+1(右+1)
。标题和URL为 未公布。 将结果数据整理为上述简化的案例外观 可视化时如下所示:
0 : Magnum公司 [ 类型=名称 ] -> namespaceMagnum.html 1 : 万能::数学 [ 类型=名称 ] -> namespaceMagnum_1_1Math.html 2 : 万能::数学::矢量 [ type=类别 ] -> classMagnum_1_1Math_1_1Vector.html 三 : 万能::数学::矢量::min() [ 类型=FUNC ] ->
classMagnum_1_1Math_1_1Vector.html#af029f9f7810201f0bd8d9580af273bde 4 : 万能::数学::范围 [ type=类别 ] -> classMagnum_1_1Math_1_1Range.html 5 : 万能::数学::范围::min() [ 类型=FUNC ] ->
classMagnum_1_1Math_1_1Range.html#ad4919361a2086212fac96da0221e4dcd 6 : 万能::数学::min() [ 类型=FUNC ] ->
namespaceMagnum_1_1Math.html#ae22ef0cb2a5a5e4c5e626a3df670be21
同样,由于数据可视化,很明显 许多形式上的冗余对数据大小产生了负面影响。 这个 形式上的重复主要是在修复之前,所以我重新使用了已经存在的 人们试图实现这一目标。 下面的视频显示 结果——这里的细节太单调了,无法完全解释,但是 基本上,每个条目标题都可以由其他条目标题预先确定 (注释人: 前缀=M
)然后取第一个 [:N]
来自该条目URL的字符。
0 : Magnum公司 [ 类型=名称 ] -> namespaceMagnum.html 1 : ::数学 [ 前缀=0[:15] , 类型=名称 ] -> _1_1 数学.html 2 : ::矢量 [ 前缀=1[:0] , type=类别 ] -> classMagnum_1_1Math_1_1Vector.html 三 : ::分钟() [ 前缀=2[:34] , 类型=FUNC ] -> #af029f9f7810201f0bd8d9580af273bde 4 : ::范围 [ 前缀=1[:0] , type=类别 ] -> 类Magnum_1_1Math_1_1Range.html 5 : ::分钟() [ 前缀=4[:33] , 类型=FUNC ] -> #ad4919361a2086212传真:96da0221e4cd 6 : ::分钟() [ 前缀=1[:28] , 类型=FUNC ] -> #ae22ef0cb2a5a5e4c5e626a3df670be21
在这种特殊情况下,映射大小从494字节下降到321字节。 在 就Magnum数据而言,节省的数据超过了兆字节。
手中拿着二进制数据,我终于做好了离开Python世界的准备 启动客户端JavaS脚本程序。 被Emscripten的方式所激励 我能够用最小的垃圾收集或卷入来运行巨大的代码库 直接接触到键入的射线并直接打开整个搜索 二进制数据。
撇开细节不谈,客户端搜索功能的工作原理很简单 将点传递到根trie节点,然后对搜索进行求和 每次输入一个8位字符。 孩子的抬头让我们看到 简单的线性搜索(根据用户的建议),因为这很快 足够了并且 不是瓶颈 儿童数量有一些合理的增长 绑定(甚至不是256,更像是26左右),所以 这个查找是
\数学{O}(n)
,与搜索字符串大小保持一致。 速度很快。
然而,只需进行一次查找,搜索结果就会显示出来,前提是用户 输入整个搜索字符串(例如 最小值 ()
). 那应该是 浪费用户的时间,而不是真正提高API的可用性。
lookahead自动组合正在进行第一次宽树搜索, 一路选择结果,最短结果优先。 有了以上内容 尝试,搜索 米
,自动组件将选择以下内容 输出:
[{ 标题 : '万能::数学::min()' , //匹配“min”后缀
网址 : “namespaceMagnum_1_1Math.html#ae22ef0cb2a5a5e4c5e626a3df670be21” },
{ 标题 : '万能::数学::范围::min()' //匹配“min”后缀
网址 : “classMagnum_1_1Math_1_1Range.html#a22af2191e4ab88b45f082ef14aa45185” },
{ 标题 : 'Magnum::Math::Vector::min()' //匹配“min”后缀
网址 : “classMagnum_1_1Math_1_1Vector.html#af029f9f7810201f0bd8d9580af273bde” },
{ 标题 : '万能::数学' , //匹配“数学”后缀
网址 : 'namespaceMagnum_1_1Math.html' },
{ 标题 : “万能” , //匹配`Magnum`后缀
网址 : “namespaceMagnum.html” },
{ 标题 : '万能::数学::min()' //匹配“数学::min”后缀
网址 : “classMagnum_1_1Math_1_1Vector.html#af029f9f7810201f0bd8d9580af273bde” },
{ 标题 : '万能::数学::范围' , //匹配“数学::范围”后缀
网址 : “classMagnum_1_1Math_1_1Range.html” },
...
不像查找搜索字符串, 这 是瓶颈,而且 关于Magnum的一切 米
(不仅仅是C++符号,不要 有关预处理器macros、目录和文件的信息),此类自动化组件 可以在第一个字符上显示无结尾的结果页面。
看看上面,有些东西似乎不对劲。它显然不是用户的 期望了解在测试时遇到的所有符号 一堆字符。 例如,当我搜索名称空间时,我没有 在其所有成员中进行了测试。 或者当一个预先修复程序同时应用于 函数名称和关闭的命名空间,我会在列表中得到两次结果。
那么如何解决这个问题呢? 在向trie中添加符号时,让我们将条形图添加到 标志着它不应该深入挖掘的外观自动组合 可能的结果。 无论如何,我不想预先公布API数据,所以当 输入,例如。 万能:
,搜索应该越过栏并显示全部 名称空间的成员。 (但不是特定成员!)
在下面的视图中 $
字符注释 lookahead酒吧。 如果自动组件到达前面的符号 这样,它就不会再有诱惑力了。
镁铝合金 [ 0 ]
||| : $
||| :数学 [ 1 ]
||| : $
||| :矢量 [ 2 ]
||| | : $
||| | :分钟 [ 三 ]
||| 范围 [ 4 ]
||| | : $
||| | :分钟 [ 5 ]
||| 最小值 [ 6 ]
|| 第个 [ 1 ]
||| : $
||| :矢量 [ 2 ]
||| | : $
||| | :分钟 [ 三 ]
||| 范围 [ 4 ]
||| | : $
||| | :分钟 [ 5 ]
||| 最小值 [ 6 ]
| 在里面 [ 三 , 5 , 6 ]
矢量 [ 2 ]
| : $
| :分钟 [ 三 ]
范围 [ 4 ]
| : $
| :分钟 [ 5 ]
有了这些酒吧,搜索 米
给出了以下输出 -独特的符号 米
从最短到最长:
[{ 标题 : '万能::数学::min()' ,
网址 : “namespaceMagnum_1_1Math.html#ae22ef0cb2a5a5e4c5e626a3df670be21” },
{ 标题 : '万能::数学::范围::min()' ,
网址 : 'classMagnum_1_1Math_1_1Range.html#a22af2191e4ab88b45f082ef14aa45185' },
{ 标题 : 'Magnum::Math::Vector::min()' ,
网址 : 'classMagnum_1_1Math_1_1Vector.html#af029f9f7810201f0bd8d9580af273bde' },
{ 标题 : '万能::数学' ,
网址 : 'namespaceMagnum_1_1Math.html' },
{ 标题 : “万能” ,
网址 : “namespaceMagnum.html” }]
搜索 数学
给出了以下内容:
[{ 标题 : '万能::数学' ,
网址 : 'namespaceMagnum_1_1Math.html' }]
搜索时 数学:
提供其所有 即时消息 成员:
[{ 标题 : '万能::数学::min()' ,
网址 : “namespaceMagnum_1_1Math.html#ae22ef0cb2a5a5e4c5e626a3df670be21” },
{ 标题 : '万能::数学::范围' ,
网址 : “classMagnum_1_1Math_1_1Range.html” },
{ 标题 : 'Magnum::Math::Vector' ,
网址 : “classMagnum_1_1Math_1_1Vector.html” }]
在这篇文章中,我试图主要阐述搜索的关键方面 改进。 剩下的也很重要,但会让作品太长 和钻孔。
为了正确地高亮显示当前键入的搜索部分 结果,当 获得结果。 这是为了显示而进行的扩展 功能部件列表和 常数
/r值过载( 不是trie数据的一部分)。
由于 基于Chromim的浏览器的限制 , 不可能下载数据 XMLHttp请求
送达时 从本地文件系统。 因为这是一个非常常见的用例,我不得不 通过将二进制转换为Base85编码来解决这个问题 *.js 文件 直接通过加载 < 脚本 >
.
客户端用户体验中有很多功能,比如键盘导航、剪切 关闭道具侧过长的预修复,避免页面跳转等 还有一些已知问题有待解决。
根据Magnum文档的当前状态,其中有7565个是唯一的 symbols和我正在添加 @m_keywords(关键字)
到所有OpenGL包装 API,数据大小如下,取决于启用的功能:
数据类型
未按下
Gzipped公司
基线,二进制
2635.6千字节
884.2千字节
子树合并,二进制
2033.3千字节
537.8千字节
子树和修复前建模,二进制
959.3千字节
501.5千字节
子树和修复前建模,base85
1202.5千字节
752.5千字节
由于文档是通过网络服务器处理的,所以我可以使用二进制和表 服务器端gzip通信。 这使其略低半兆字节, 与传统网站的规模相比,这一点不足为奇。 这个 文档每周更新不超过一次,因此浏览器将 通常只是从缓存中很好地服务这个数据集。
需要注意的一件重要事情是:预修复并没有提供太多 在计算gzipped大小时非常有用,因为数据已经很好了 可发布。
有人可能会说,上述对8位字符的限制使得 不是统一代码软件,因此使用更少的简单AS-CII符号。 嗯,没有。 总的来说,输入是UTF-8编码的,这是两个伟大的捷克人的比赛 话 “哈扎德” 和 “hárá” 将如下所示:
小时 0xc3 0xbd(0xbd) 0xc5(0xc5) | 0xbe(0xbe) | d日 0xc4(0xc4) | 0x9b个 | [ 13 ]
0xa1个 第页 0xc3个 | 0xa1个 | [ 42 ]
该功能只需使用UTF-8表示单词 “哈扎德” [ “h” , 0xc3个 , 0xbd(0xbd) , 0xc5(0xc5) , 0xbe(0xbe) , “d” , 0xc4(0xc4) , 0x9b个 ]
字节-字节和插入到trie中,没有太多考虑 无论给定字符串是什么。这也有UTF-8的效果 代表 “ý” 和 “á” 是 [ 0xc3个 , 0xbd(0xbd) ]
和 [ 0xc3个 , 0xa1个 ]
尊敬的是,首先分享相同的内容 字节 0xc3个
,这是trie中的单个节点。这意味着没有 需要有超过8个比特来表示trie中的字符。
要精确,并且 /u/罗宾-m 在Red dit上发表了一篇文章,这是一篇完全符合统一代码意识的文章 需要改进 非等价值 例如,为了将 á
以与 á
分别由 一
和 ´
代码点。 就像你可能做的那样 已经猜到,这不是一项容易完成的任务和常见的影响 包括多个兆字节传输表来实现这一点。
因为这是一个非常罕见的问题(即使是 搜索UTF-8是一种罕见的情况,因为大多数符号都使用as-CII 无论如何),我决定不再两者兼而有之。