大量OCR期間的記憶體高峰

This article was translated from English: Does it need improvement?
Translated
View the article in English

同時在許多PDF片段上執行OCR會成倍增加記憶體使用。 每個任務通過IronTesseract引擎每次重新載入語言模型文件。在完整處理器並發的情況下,這會將記憶體高峰推到數GB範圍,並在記憶體有限的環境中出現失敗的峰值。

OCR本質上是記憶體密集型的。 每個IronTesseract引擎將語言模型文件載入到記憶體中。 為每個片段建立一個新引擎會重複載入這些模型,並且每個CPU核心執行一個OCR任務 (Environment.ProcessorCount) 允許許多位圖密集型工作並行運行。由於沒有限制有多少任務處於活動狀態,記憶體高峰直接隨並發程度縮放。

解決方法是限制正在進行的工作的數量:限制並發,重用池中的引擎,並使用信號量控制工作。

解決方案

1. 限制OCR並發性

將同時OCR任務的數量限制到一個小的上限。 更少的並發任務意味著記憶體中同時存在的全頁位圖更少,這直接降低了記憶體高峰。 根據機器的能力調整上限。

// Clamp concurrency to avoid memory saturation and CPU over-subscription.
int concurrency = Math.Clamp(Environment.ProcessorCount / 2, 1, 4);
// Clamp concurrency to avoid memory saturation and CPU over-subscription.
int concurrency = Math.Clamp(Environment.ProcessorCount / 2, 1, 4);
Imports System

' Clamp concurrency to avoid memory saturation and CPU over-subscription.
Dim concurrency As Integer = Math.Clamp(Environment.ProcessorCount \ 2, 1, 4)
$vbLabelText   $csharpLabel

2. 引擎池化

在啟動時為每個並發槽位建立一個IronTesseract引擎,並在每個片段中重用它們,而不是每次構造新引擎並重新載入語言模型。

// Pre-create one engine per concurrent slot and reuse them across segments.
var enginePool = new ConcurrentBag<IronTesseract>(
    Enumerable.Range(0, concurrency).Select(_ => new IronTesseract())
);
// Pre-create one engine per concurrent slot and reuse them across segments.
var enginePool = new ConcurrentBag<IronTesseract>(
    Enumerable.Range(0, concurrency).Select(_ => new IronTesseract())
);
' Pre-create one engine per concurrent slot and reuse them across segments.
Dim enginePool As New ConcurrentBag(Of IronTesseract)(
    Enumerable.Range(0, concurrency).Select(Function(_) New IronTesseract())
)
$vbLabelText   $csharpLabel

一次構建池將語言模型載入成本攤在整個運行過程中,而不是每個片段分別支付。

3. 使用信號量控制工作

初始化using中。 每個任務在開始前會調用Release(),以便同時飛行的片段數量不超出允許的數量。

using var semaphore = new SemaphoreSlim(concurrency);
await semaphore.WaitAsync();
try
{
    // Rent a pre-loaded engine from the pool.
    if (!enginePool.TryTake(out var ocr))
        ocr = new IronTesseract(); // Defensive fallback; should never be reached.
    try
    {
        using var input = new OcrInput();
        input.LoadPdf(segmentStream); // page-range segment produced upstream
        var ocrResult = await ocr.ReadAsync(input);
        ocrResult.SaveAsSearchablePdf(outputPath);
    }
    finally
    {
        enginePool.Add(ocr); // Return engine to pool for the next waiting segment.
    }
}
finally
{
    semaphore.Release();
}
using var semaphore = new SemaphoreSlim(concurrency);
await semaphore.WaitAsync();
try
{
    // Rent a pre-loaded engine from the pool.
    if (!enginePool.TryTake(out var ocr))
        ocr = new IronTesseract(); // Defensive fallback; should never be reached.
    try
    {
        using var input = new OcrInput();
        input.LoadPdf(segmentStream); // page-range segment produced upstream
        var ocrResult = await ocr.ReadAsync(input);
        ocrResult.SaveAsSearchablePdf(outputPath);
    }
    finally
    {
        enginePool.Add(ocr); // Return engine to pool for the next waiting segment.
    }
}
finally
{
    semaphore.Release();
}
Imports System.Threading
Imports IronOcr

Dim semaphore As New SemaphoreSlim(concurrency)
Await semaphore.WaitAsync()
Try
    ' Rent a pre-loaded engine from the pool.
    Dim ocr As IronTesseract = Nothing
    If Not enginePool.TryTake(ocr) Then
        ocr = New IronTesseract() ' Defensive fallback; should never be reached.
    End If
    Try
        Using input As New OcrInput()
            input.LoadPdf(segmentStream) ' page-range segment produced upstream
            Dim ocrResult = Await ocr.ReadAsync(input)
            ocrResult.SaveAsSearchablePdf(outputPath)
        End Using
    Finally
        enginePool.Add(ocr) ' Return engine to pool for the next waiting segment.
    End Try
Finally
    semaphore.Release()
End Try
$vbLabelText   $csharpLabel

finally中返回引擎則會將預載入的引擎直接交給下一個等待的片段。

4. 每個片段處置OcrInput

將每個using中,因此在片段被讀取的時候釋放其呈現的頁面位圖,在下一個任務佔用槽位之前。

提示using 上的 OcrInput 是防止跨片段累積位圖記憶體的原因; 沒有它,釋放的槽位仍會保留其頁面位圖。

Curtis Chau
技術作家

Curtis Chau擁有Carleton大學的電腦科學學士學位,專精於前端開發,擁有Node.js、TypeScript、JavaScript和React的專業知識。Curtis熱衷於建立直觀且美觀的使用者介面,喜愛使用現代框架並建立結構良好、視覺吸引力的手冊。

除了開發,Curtis對物聯網(IoT)有濃厚的興趣,探索創新的方法來整合硬體和軟體。在空閒時間,他喜歡玩遊戲和建立Discord機器人,結合他對技術的熱愛與創造力。

準備開始了嗎?
Nuget 下載 6,136,090 | 版本: 2026.7 剛剛發布
Still Scrolling Icon

還在滾動?

想要快速證明? PM > Install-Package IronOcr
執行範例 觀看您的圖像轉變為可搜尋文字。