From Evernote:
3.5中TermInfosReader的改进
Clipped from: alires:///MsgHistory/recent_tribe.htm?cssname=default
Ref:
1. https://issues.apache.org/jira/browse/LUCENE-2205
2. http://www.nearinfinity.com/blogs/aaron_mccurry/my_first_lucene_patch.html
原来思路:
- private final Term[] indexTerms;
- private final TermInfo[] indexInfos;
- private final long[] indexPointers
1) 利用这三个字段来保存Term相关信息。
2) 每隔128保存一个Term信息,因此保存的实际上是总共term的 1/128 。
3) 因为term是有顺序的,在查找一个term的位置时,使用的是binary search, 在比较时使用的是String 的compare
3.5中修改点:
1. 将 term, termInfo indexPointer信息保存到 TermInfosReaderIndex 类中。
2. Term中包含的信息:
String field; (1) 哪个字段
String text; (2) 什么内容
TermInfo 中包含的信息:
/** The number of documents which contain the term. */
int docFreq = 0; (3)
long freqPointer = 0; (4)
long proxPointer = 0; (5)
int skipOffset; (6)
3. TermInfosReaderIndex 中的处理:
for (int i = 0; indexEnum.next(); i++) {
Term term = indexEnum.term();
if (currentField != term.field) {
currentField = term.field;
fieldStrs.add(currentField);
fieldCounter++;
}
TermInfo termInfo = indexEnum.termInfo();
indexToTerms.set(i, dataOutput.getPosition());
dataOutput.writeVInt(fieldCounter); //(1)来代替term中的field
dataOutput.writeString(term.text()); // (2) 来代替 term中的text
dataOutput.writeVInt(termInfo.docFreq); //(3) termInfo中的docFreq
if (termInfo.docFreq >= skipInterval) {
dataOutput.writeVInt(termInfo.skipOffset); //(6) termInfo中的 skipOffset,在某种情况下被省略掉
}
dataOutput.writeVLong(termInfo.freqPointer); //(4) termInfo中的freqPointer
dataOutput.writeVLong(termInfo.proxPointer); //(5) ternInof中的proxPointer
dataOutput.writeVLong(indexEnum.indexPointer);
for (int j = 1; j < indexDivisor; j++) {
if (!indexEnum.next()) {
break;
}
}
}
从这里可以看出除了field内存使用fieldCount来代替之外,其它都是没有变化。
term中field使得int来代替有一个好处是尽量使用number来代替string,同时对number和string保持一个映射关系。
诱饵:
前段时间在 jvm 群有提到在系统启动里Forest,Catserver会占用大量内存,导致full gc。
后来有同学 提到 :
我们在做ip查找的时候,把所有的string放一起了。。。。
用的时候来new string
用数组压缩掉
对象少多了
而且可以减少重复string。
这样做, 就会成为 3个数组对象+一个string对象
gc毫无压力
数组对象主要是 ip地址对应的属性,各种距离。
哈哈,面对这类场景,我很自然地又想到了 lucene-2205 这个issue,因为我本身对这个issue中某些点印象比较深刻:
这是3.5中的一个重大的 optimization, 当时官方release说明中是: Very substantial (3-5X) RAM reduction required to hold the terms index on opening an IndexReader
这个issue 从created 到resolved在历时一年多,当然在lucene中历时这么久的也还有其它issue
从这个issue也说明了只要去思考,提出方案,然后在别人指导下,还是可以取得不错成绩的。这issue的提出及解决者就是第一次向lucene社区提交patch,
针对这个issue, Doug Cutting 也出来冒泡了,提了好几点意见:
It’d probably be better not to make TermInfosReaderIndex and its subclasses public, to reduce the APIs that must be supported long-term.
针对这点,我们大部分同学是否有所感想? 因为我目前看到的是:很多人只习惯public,private ,至于是否真的需要public有考虑过这么多吗?针对这个public,这次法洛斯项目中,也是被坑了一次,完全可以写点总结。
Term : 简单来说就是用来表示某个field对应的一个word,(因为分词,一个field可以有多个word,因此也会有多个term)
因为有权重,高亮,span query等功能,需要表示这个term的相关信息,会有TermInfo对象:
lucene 3.5 及之前版本中,对term相关信息在内存中的保存方式:
实现类: TermInfosReader
用来保存term信息的是:
private final Term[] indexTerms ;
private final TermInfo[] indexInfos;
private final long [] indexPointers;
这也是我们平常一般思维习惯上组织信息的方式,分别用3个数组来表示整个索引对应的term信息。
在对象初始化时,从文件中读取相应信息填充这三个数组:
让我们从基本知识点来考虑内存占用情况:
Terms[]
TermInfos[]
long[]
如果从对象上来说,Term, TermInfos中都没有一丁点多的属性,也没有属性可以合并之类的优化方案了。同时对象中也都已经使用了primitive类型(String 除外啊,:))。
java占用内存,很容易被忽略的一点是reference。
任何单一的引用会占用 4个字节(32位机器)or 8字节(64位机器)。从引用角度来看,我们可以算一下保存term信息的这几个数组会占用多少内存
Terms[] 占用内存是 length * 3, 其中一个引用是Term对象,另外两个是Term中的String 属性
TermInfos[] length*1, 只是TermInfos占用一个引用,因为其内部都是primitive
long[] 只占用 1 reference, 可以忽略了吧
因此假设索引中有10亿个term(不要觉得很夸张,一个document中可能包含几十个term,如果以一个doc中包含50个term来算,只需要2KW document), 在 32位机器上,保存这部分内存中的term(对,是内存中的term,并不是全部term)需要 125M = 1,000,000,000/128 *(3+1 reference)*4bytes,在目前都是64位天下的情况下,就会占用 250M。 如果把128变成 64,那在64位机器下就是500M占用,这可是只是reference占用的内存啊。
如何把这些reference占用的内存省下来呢? 砍掉对象,在内存中不再以对象方式组织信息,以bytesArray来保存原始信息,同时为了能反向找出对象信息,偏移量信息的int数组是必须的
我们再看一下这样的思考来保存term相关信息的话,实际代码是如何实现的:
有了偏移量,在get时,查找相关term信息就不会是问题啦,主要是也不想再写了,哈哈。