我是怎么用跳表优化搜索引擎的?

Posted haolujun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我是怎么用跳表优化搜索引擎的?相关的知识,希望对你有一定的参考价值。

前言

对于跳表,我想大家都不陌生吧,这里不多解释,感兴趣的小伙伴可以看我的这篇文章:http://www.cnblogs.com/haolujun/archive/2012/12/24/2830683.html。 这段时间在做我们拍搜的优化,今天我就讲讲我是如何用跳表优化检索系统的。

搜索引擎的夹角余弦计算

都知道,搜索引擎利用夹角余弦计算query与文档的相似度,感兴趣的小伙伴可以看我的这篇文章:http://www.cnblogs.com/haolujun/archive/2013/01/08/2847503.html, 这里面需要计算两个向量的余弦值。

假设查询向量为:$ Q = [q_{1},q_{2},......,q_{n}] \\( 假设文档向量为:\\) D = [d_{1},d_{2},......,d_{n}] $

query与文档的相似度为:$ sim(Q,D) = \\frac{QD}{|Q||D|} $ 。这里面需要Q和D模长相乘做分母,对应分量相乘之和做分子。

模长的计算

对于query可以在每次查询之前做统一的预处理,在预处理过程中计算模长;对于文档,不能每次查询都计算一遍模长,这样效率很低,可以事先在建立索引的时候计算模长并保存。

向量相乘

在搜索引擎检索过程中,首先需要对query进行分词并得到查询向量,之后我们用分出的词从倒排表中拉取文档,不熟悉倒排表的小伙伴可以看这篇文章:http://www.cnblogs.com/haolujun/archive/2013/01/06/2847510.html。 通过倒排拉取出来的只是文档的ID,而文档本身包含的内容其实是以正排的方式存储的:即key=文档ID,$value=(word_{1}, word_{2}, ....word_{n}) $,实际上为了节省存储空间,正排只存储该文档中出现过的词。而今天的向量相乘就是利用正排与query进行两个集合的求交计算(向量相乘只对同时出现在query以及文档中的词进行计算,其它的都计为0),那么这就引出一个新问题,如何求两个集合的交集呢?聪明的小伙伴肯定想到了解决办法:对Q和D分别按照字典序排序,之后求两个有序列表的交集可以在线性时间内完成,示例代码如下:

int i = 0, j = 0;
double sum = 0.0;
while(i < len_q && j < len_doc) {
    if(q[i] < d[j]) {
        i++; 
    } else if(q[i] > d[j]) {
        j++;
    } else {
        sum += sim(q[i], d[j]); 
    }
}

但是,还能再优化这个代码么?答案是肯定的,那就是利用跳表。

对于搜索引擎来说,通常query较短,而文档较长,我们估计排完序的文档序列中会有一大段一大段的词都不在query中,可以直接跳过这些段而不用一一遍历,这就启发我们可以用跳表进行加速,优化代码如下:

double sum = 0.0;
int step = ceil(sqrt(len_doc)), i = 0, j = 0;
while(i < len_q && j < len_doc) {
   if(q[i] < d[j]) {
      int k = (j - step + 1) < 0 ? 0 : j - step + 1;
      while(k <= j && d[k] < q[i]) k++;
      if(d[k] == q[i]) {
        sum += sim(i, j); j = k + 1;  i++;
      } else {
        j = k; i++;
      }
    } else if(q[i] > d[j]) {
	  j = j + step < len_doc ? j + step : j + 1;
    } else {
      sum += sim(i, j); 
	  ++i;
	  j = j + step < len_doc ? j + step : j + 1;
    }
  }
}

我们在这里选择步长为 $ \\sqrt[]{len_{doc}} $只是表示一种理论指导,实际中还需要不断测试不同的步长从而找到实际最优解。

当然,优化这个问题并不一定用跳表,如果内存足够大,我们可以为每个文档建立哈希或者map,这样只需用O(1)或者 O(log(len_doc))时间判断文档中是否包含一个词。

总结

利用简单的跳表,加起来不过几十行代码,直接使检索的效率提高一倍,优化了50%的硬件成本,可以高高兴兴领取年终奖回家过大年了。 5年前我就研究了跳表,没想到5年后的今天我竟然用到了它,所以没事多看看感兴趣的技术、研究一下感兴趣的问题对你的将来只有利没有弊。

以上是关于我是怎么用跳表优化搜索引擎的?的主要内容,如果未能解决你的问题,请参考以下文章

Redis 为什么用跳表,而不用平衡树?

SkipList 跳表

SkipList跳表基本原理

SkipList跳表基本原理

跳跃表原理

ConcurrentSkipListMap以及跳表的原理