Home » , » Lucene的检索优化(二)–Hits的改进

Lucene的检索优化(二)–Hits的改进

via 笨笨的小田园 by 笨笨 on 8/15/08
刚刚开始学Lucene,看的是Lucene in
Action。顺着看下去,很自然的就是使用Hits来访问Search的结果。但是使用起来,发现Search的速度是很快,不过如果结果很多的话(比如1W个),通过Hits访问所有的结果速度非常慢,就是简单地从每个结果中读一个Field,在我的机器上用了接近2分钟。因为我的应用索引的只是我的数据的两个域包含文本信息的域,我本希望通过Lucene查找出符合需求的数据ID,再通过ID去判断数据库中的其他域来决定最终的结果。这样连取ID就需要2分钟,我的应用可受不了。
第一个想到的方法是把我的全部数据域都做成Lucene的索引,然后全部通过Lucene去搜索。但是由于我的很多域是数字,全部转换成Lucene能接受的字符串,感觉性能不会好。另外如果我想针对搜索的结果做统计,也没法避免需要遍历全部的搜索结果,如果1W个结果就需要2分钟的话,就算不用处理其他的域,也是不能忍受的。
开源软件的好处就是可以读代码。通过阅读Hits的代码,终于找到了解决问题的办法。
Lucene
的代码看起来并不是特别Professional。比如下面这两个Hits的初始化函数。首先里面的q,s,f什么的让人看起来就不是太舒服(其他的代码里还用i,j做循环变量)。其次这两个函数只有o那一个赋值不一样,明显应该只写一个,让另一个来调用。最后程序里面直接用了50这个常数,编程的大忌。(50在其他函数里面也有)
Hits(Searcher s, Query q, Filter f) throws IOException {
    weight =
q.weight(s);
    searcher =
s;
    filter =
f;
    nDeletions =
countDeletions(s);
  
getMoreDocs(50); // retrieve 100 initially
  
lengthAtStart = length;
  }
  Hits(Searcher s, Query q, Filter f, Sort o)
throws IOException {
    weight =
q.weight(s);
    searcher =
s;
    filter =
f;
    sort =
o;
    nDeletions =
countDeletions(s);
  
getMoreDocs(50); // retrieve 100 initially
  
lengthAtStart = length;
  }
通过这两个函数,应该看出Hits初始化的时候只调入了前100个文档。
一般我们是通过Document doc(int
n)函数来访问的。这个函数里面先判断了有多少数据已经被调入了,如果要访问的数据不在,就去调用getMoreDocs函数,getMoreDocs会取得需要的2倍文档进来。
但是getMoreDocs的代码比较让人疑惑,里面一段代码是这样的:
    int n = min
* 2;    //
double # retrieved
    TopDocs
topDocs = (sort == null) ? searcher.search(weight, filter, n) :
searcher.search(weight, filter, n, sort);
这不成了每次翻倍的时候都要去调search重新查找吗?除非search里面有缓存,否则性能一定指数下降啊!
实际上Hits最终使用的也是TopDocs,Searcher组合来实现输出结果,那不如我们来直接使用下层一点的对象了。我原来的代码是:
Hits hits = searcher.search(query);
for( int i=0;i    Document doc
= hits .doc(i );
  
szTest.add(doc);
}
现在改为:
TopDocs topDoc = searcher.search(query.weight(searcher), null,
100000);//注意最后一个参数,是search返回的结果数量,应该比你最大可能返回的数量大,否则ScoreDoc里面就是你设置的数量。
ScoreDoc[] scoreDocs = topDoc.scoreDocs;
for( int i=0;i    Document doc
= searcher.doc(scoreDocs[i].doc );
  
szTest.add(doc);
}
结果把12000个ID加入ArrayList用时0.4秒,快了几百倍。
等等,还没完。
我只需要ID字段,但是返回整个Doc,其他两个文本Field也返回了。因为Lucene是倒索引保存信息的,每一个文本Field需要重新组合成原始的字符串,这也是要耗时间的。searcher的doc函数有一个可以限定只取部分域的:
Document doc(int n, FieldSelector fieldSelector)
我下面定义一个FieldSelector,只取某一个给定名字的Field
class SpecialFieldSelector implements FieldSelector {
    protected
String m_szFieldName;
    public
SpecialFieldSelector( String szFieldName ) {
      
m_szFieldName = szFieldName;
    }
  
    public
FieldSelectorResult accept(String fieldName) {
      
if( fieldName.equalsIgnoreCase(m_szFieldName)) {
          
return  FieldSelectorResult.LOAD;
      
}
      
else {
          
return  FieldSelectorResult.NO_LOAD;
      
}
  
}  
}
再修改我的代码:
ScoreDoc[] scoreDocs = topDoc.scoreDocs;
ArrayList szTest = new
ArrayList();
FieldSelector fieldSelector = new
SpecialFieldSelector(FIELD_ID);
for( int i=0;i      
Document doc = searcher.doc(scoreDocs[i].doc, fieldSelector);
      
szTest.add(doc);
}
现在返回1.2W个ID耗时0.25秒。虽然比前面只少了大约150毫秒,但是是接近40%的提高了,在负载比较大的应用中还是很重要的。

3 Comments:

Unknown said...

Let's understand the things going on with the determining. because I am not able to concrete myself as a professional writer but I am providing the PhD thesis services, nothing to loose and nothing to find the new and fresh content without the help of any student.

Rony James said...

This is a great article for anyone looking for information on how to optimize Lucene for their law essay writers needs. The author provides clear and concise instructions on how to get the most out of Lucene and includes helpful tips on troubleshooting and optimization.

Tunji D said...

Very nice posst

Popular Posts