バルクローバルOCR中のハイピークメモリー

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

一度に多くのPDFセグメントに対してOCRを実行することはメモリー消費を倍増させます。 各タスクはIronTesseractエンジンが言語モデルファイルを毎回リロードします。フルプロセッサーの同時実行性では、これによりメモリピークが数GB範囲に達し、メモリに制限のある環境でのスパイクが失敗します。

OCRは生来メモリを多く使用します。 すべてのIronTesseractエンジンは言語モデルファイルをメモリーにロードします。 各セグメントごとに新しいエンジンを作成すると、それらのモデルが繰り返しリロードされ、CPUコアごとに1つの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. エンジンをプールします

起動時に同時スロットごとに1つだけ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でラップして、そのセグメントが読み取られるとすぐにそのレンダードページビットマップが解放され、次のタスクがスロットを獲得する前に解放されます。

ヒントセグメント間でビットマップのメモリが蓄積されないようにするのが、usingOcrInput です; それなしでは、解放されたスロットもページビットマップを保持します。

Curtis Chau
テクニカルライター

Curtis Chauは、カールトン大学でコンピュータサイエンスの学士号を取得し、Node.js、TypeScript、JavaScript、およびReactに精通したフロントエンド開発を専門としています。直感的で美しいユーザーインターフェースを作成することに情熱を持ち、Curtisは現代のフレームワークを用いた開発や、構造の良い視覚的に魅力的なマニュアルの作成を楽しんでいます。

開発以外にも、CurtisはIoT(Internet of Things)への強い関心を持ち、ハードウェアとソフトウェアの統合方法を模索しています。余暇には、ゲームをしたりDiscordボットを作成したりして、技術に対する愛情と創造性を組み合わせています。

準備はできましたか?
Nuget ダウンロード 6,136,090 | バージョン: 2026.7 リリースされたばかり
Still Scrolling Icon

まだスクロールしていますか?

すぐに証拠が欲しいですか? PM > Install-Package IronOcr
サンプルを実行 あなたの画像が検索可能なテキストになるのをご覧ください。