首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

Lucene 3.0.0 核心細節探秘(二):索引過程中的記憶體緩衝池機制

     編輯:關於.NET

前言

Lucene 索引時如何節省記憶體、合理分配緩衝區一直困擾我。早期用 C++ 無記憶體池導致嚴重碎片與換頁,3MB 文本分詞後暴漲至 70MB。本文深入分析 Lucene 3.0.0 的三大緩衝池設計。

三大緩衝池架構

在 TermsHashPerField 中有三個池:

  • CharBlockPool:儲存術語文本
  • IntBlockPool:儲存位置資訊的起始索引
  • ByteBlockPool:儲存位置資訊(每位置 1 byte)

三者結構高度相似,均來自 TermsHashPerThread,每執行緒獨立擁有,利於多核並行索引。

1. CharBlockPool 詳解

核心欄位


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 記錄術語在緩衝池中的全域起始位置。

2. IntBlockPool 與 ByteBlockPool 協作

兩者緊密結合:

  • 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 不足:

  1. 分配更大 slice(參考 levelSizeArray[])
  2. 將現有資料複製至新 slice
  3. 原 slice 寫入指向新 slice 的位移

邏輯見 TermsHashPerField.writeByte。類比:


// 原狀態
AA ... B(offset) C ...
// B 只有 3 位元組剩餘,需寫入 4 位元組
// 擴容後
AA ... B(offset) C ... (new B): B B B B

3. 設計反思

  • 三大池程式碼高度重複,建議統一抽象基類
  • 註解稀少,增加維護難度
  • 廣泛使用 RawPostingList[] 陣列式雜湊而非泛型 HashMap,避免排序開銷與 RTTI 問題

版權聲明

本文著作權歸 LeftNoteasy 所有,轉載請保留此聲明並註明出處。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved