Lucene 索引時如何節省記憶體、合理分配緩衝區一直困擾我。早期用 C++ 無記憶體池導致嚴重碎片與換頁,3MB 文本分詞後暴漲至 70MB。本文深入分析 Lucene 3.0.0 的三大緩衝池設計。
在 TermsHashPerField 中有三個池:
CharBlockPool:儲存術語文本IntBlockPool:儲存位置資訊的起始索引ByteBlockPool:儲存位置資訊(每位置 1 byte)三者結構高度相似,均來自 TermsHashPerThread,每執行緒獨立擁有,利於多核並行索引。
final class CharBlockPool
{
char[][] buffers = new char[10][]; // 二維陣列:「樓房」
int bufferUpto = -1; // 當前樓層
int charUpto = DocumentWriter.CHAR_BLOCK_SIZE; // 當前樓層位移
char[] buffer; // 當前緩衝區
int charOffset = -DocumentWriter.CHAR_BLOCK_SIZE; // 總位移
}
設計概念:二維陣列如樓房,當前層滿時向上蓋新層(nextBuffer())。
public void nextBuffer() {
if (1 + bufferUpto == buffers.length) {
char[][] newBuffers = new char[(int)(buffers.length * 1.5)][];
System.arraycopy(buffers, 0, newBuffers, 0, buffers.length);
buffers = newBuffers;
}
buffer = buffers[1 + bufferUpto] = docWriter.getCharBlock();
bufferUpto++;
charUpto = 0;
charOffset += DocumentsWriter.CHAR_BLOCK_SIZE;
}
不足時容量乘 1.5,向 docWriter 申請區塊(內部會記帳監控記憶體)。
if (p == null) {
final int textLen1 = 1 + tokenTextLen;
if (textLen1 + charPool.charUpto > DocumentsWriter.CHAR_BLOCK_SIZE) {
if (textLen1 > DocumentsWriter.CHAR_BLOCK_SIZE) { /* 處理過長術語 */ }
charPool.nextBuffer();
}
final char[] text = charPool.buffer;
final int textUpto = charPool.charUpto;
p.textStart = textUpto + charPool.charOffset;
charPool.charUpto += textLen1;
System.arraycopy(tokenText, 0, text, textUpto, tokenTextLen);
text[textUpto + tokenTextLen] = 0xffff; // 終止符
}
p.textStart 記錄術語在緩衝池中的全域起始位置。
兩者緊密結合:
ByteBlockPool 儲存位置資料(每段 5 bytes,FIRST_LEVEL_SIZE)IntBlockPool 儲存每段在 BytePool 中的起始位移
intUptos = intPool.buffer;
intUptoStart = intPool.intUpto;
intPool.intUpto += streamCount;
p.intStart = intUptoStart + intPool.intOffset;
for (int i = 0; i < streamCount; i++) {
final int upto = bytePool.newSlice(ByteBlockPool.FIRST_LEVEL_SIZE);
intUptos[intUptoStart + i] = upto + bytePool.byteOffset;
}
p.byteStart = intUptos[intUptoStart];
consumer.newTerm(p);
intUptoStart 對應的 BytePool 位移比實際 byteUpto 小一個 slice 大小(5 bytes),指向資料段起始。
寫入位置時若當前 slice 不足:
levelSizeArray[])邏輯見 TermsHashPerField.writeByte。類比:
// 原狀態
AA ... B(offset) C ...
// B 只有 3 位元組剩餘,需寫入 4 位元組
// 擴容後
AA ... B(offset) C ... (new B): B B B B
RawPostingList[] 陣列式雜湊而非泛型 HashMap,避免排序開銷與 RTTI 問題本文著作權歸 LeftNoteasy 所有,轉載請保留此聲明並註明出處。