Quantcast
Channel: IT社区推荐资讯 - ITIndex.net
Viewing all articles
Browse latest Browse all 11804

lucene MoreLikeThis性能分析

$
0
0

最近使用lucene的MoreLikeThis实现一个小型的推荐系统。语料由短文本构成,本身也还算比较中小等规模:7000w左右(亿级别)的数据量,3G大小的文件。对需要的Field建完索引后的索引文件大小在4G左右。本文只是结合自己的实践列出一些注意事项,以做为参考。

一、MoreLikeThis实现原理

根据输入文本和要检索的Field,进行like判断时,整个过程如下:

  1. 提取出terms:分词,判断:是否大于最大出现数,是否停留词,统计tf => 返回<word,int>频率表
  2. 根据tf-idf分值对term进行从大到小排序: 遍历所有fields,找到最大文档频率df的值和对应field,判断一些条件,计算tf/idf值,插入到优先级队列 => FreqQ<word,topField,score,idf,docFreq,tf>
  3. 生成Query:使用TermQuery,设置相应boost值和Occuer.SHOULD生成BooleanQuery查询.
  4. 进行search.

这里说一下第3步的boost生成策略:

首先优先级队列中score(即tf/idf值)分值最高的,为bestScore. myScore为队列中的当前score值,按照以下的公式线性衰减,myScore越小,衰减的越厉害:

boost_value = boostFactor * myScore / bestScore

得到boost后,我们就可以根据原生lucene的打分公式在search后进行相关性排序:

(q为对应的查询条件,d为对应文档)

MoreLikeThis使用的是DefaultSmilarity. (对应coord, queryNorm等等,由它与BooleanQuery来决定)

二、性能分析

lucene的首次查询可忽略。简单地写个循环,文件中读取10000条不同的测试数据,存在一个map数组中。分别对其进行检索。

对相应比较的接口进行比较:

1.使用普通的接口MultiFieldQueryParse.

2.使用MoreLikeThis接口(不使用DuplicateFilter)

3.使用MoreLikeThis接口(使用DuplicateFilter)

方式3耗时相当严重(秒级别),如果真的需要去重,建议使用hashmap去重好了。这里的接口比较中,只比较方式1和方式2.

JAVA_OPTS为:-Xmx8096m -Xms8096m -server

(8g的jvm内存空间已经足够,经测试,开得更大不会在性能性获得提升。初始化时一次性分配好。)

使用mmap加载索引,使用的环境及版本:

lucene4.7.2 + jdk1.6.0-64+4core+32Gmem+linux修改版

这里使用了3个文档数,里面含有短文本,做为搜索输入,规模分别为:100条,1000条,1w条。此处的先对比下MostLike接口,相关参数如下:

  • topN: 20
  • MinTermFreq: 0
  • MinDocFreq: 0
  • MinWordLen: 1
  • MaxQueryTerm: [1-4]

此处对最关键的MaxQueryTerm做了一个简单的性能测试,不同值下一个横向对比:

文档数MoreLike
(1,20)
平均时间MoreLike
(2,20)
平均时间MoreLike
(3,20)
平均时间MoreLike
(4,20)
平均时间
1007287ms121412ms239824ms308331ms
100031143ms965110ms3611636ms6243262ms
10000175581.7ms805848ms30536531ms56543757ms

MaxQueryTerm的长短会直接影响你对结果判断的好坏。因此,需要对该值做一个权衡。在不影响结果效果的前提下,当然追求速度越快越好,这会直接影响你线上的请求处理并发量。

为了再进行一次对比,这里列出了 MultiFieldParser的性能测试,包含了些简单逻辑处理单个字的词,停留词等,这里使用第一个为MUST,其余都为SHOULD(你也可以设计一定的规则,去自动设定搜索词的occur),其它参数:

topN: 20

文档数common(20)平均处理时间
1002923ms
10009891ms
1000036020.3ms

性能方面是远远好与MoreLikeThis。但是效果方面肯定会打一些折扣。

切换到新的版本与环境:lucene-4.9.1 + jdk7,

文档数MostLike
(1,20)
平均时间MostLike
(2,20)
平均时间MostLike
(3,20)
平均时间MostLike
(4,20)
平均时间
1007557ms126512ms245924ms315231ms
100032453ms1013510ms3646336ms6260762ms
10000182891.8ms822568ms30579531ms56470156ms

性能没有本质的提升。

接着再说一下多线程版本,还是jdk6的环境。性能测试表如下(此处算法有做了调整,只做横向对比,不与上面对比),分别处理100/1000/10000条的消息时的性能表现:

序号线程数100语料
(ms)
100处理量(ms)1000语料
(ms)
1000处理量(ms)10000语料
(ms)
10000处理量(ms)
11482148949009478224178
22323732540745444745445
34236123399284025083825
48231023375013724723625
516240524309303126123826
632226423307313126993727

相应的图为:

单cpu-4核的机器,4个线程的时候差不多是性能最好的时候。

长耗时查询

最后一个问题是长耗时查询,产生的原因也很简单。虽然MoreLikeThis会根据tf/idf取出前n个词来进行搜索,但是如果本身搜索的词就小于 n个,并且这些词还长着一张“大众脸”的话。。。duang,你一搜出来的totalHits就可能是上千万的结果集,然后根据分值做一下排序,那也会耗去不少时间。直观的感觉就是限制搜索集的数目,当满足一定量时就不再继续搜索下去。

方式一:

上面的条件中,有一个MinWordLen,如果为1的话,那么类似于单个字的倒排索引,它的文档列表会相当长(很可能几百万几千万)。将它设置为2的话,不算是查询效果以及查询性能上都有较大的提升。

4线程,MinWordLen=2的示例:

1000: 总耗时:16351ms,   平均:16ms

10000:  总耗时:392398ms, 平均:39ms

当你去跑上面的文档时,虽然降了不少,效果也提升不少。但是还是有一部分还是有些耗时(理论上问题仍存在,只是出现的机率下降了不少)。下面引出方式二。

方式二:

MoreLikeThis中还存在着一个参数,maxDocFreq。缺省值为32位的1. 我们可以设置成50w。该参数可以忽略过出现超过50w的查询。这个时候,你的查询性能将大大获得提升。完全可以满足实时查询的要求。

方式三:

由源码级别可知:TopScoreDocCollector的collect(). BooleanScorer2的score. 这两者,有能力的话自行修改代码. (通过继承等手段实现略难),就不推荐使用了。

排序公式部分:来自《annotated lucene》.


Viewing all articles
Browse latest Browse all 11804

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>