대량 OCR 중 높은 피크 메모리

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

한 번에 많은 PDF 세그먼트에 대해 OCR을 실행하면 메모리 사용량이 증가합니다. 각 작업은 OcrInput를 통해 전체 페이지 비트맵을 렌더링하며, 세그먼트당 새로운 IronTesseract 엔진이 매번 언어 모델 파일을 새로 로드합니다. 프로세서 동시도가 최대일 때 이로 인해 메모리 제한이 있는 환경에서 실패할 수 있는 멀티 GB 범위의 메모리 피크가 발생합니다.

OCR은 본질적으로 메모리 사용이 많은 작업입니다. 모든 OcrInput은 전체 페이지 비트맵을 렌더링하고, 모든 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. 세마포어로 작업 제어

동시성 제한에 SemaphoreSlim을 초기화하고 이를 using로 묶습니다. 각 작업은 시작하기 전에 WaitAsync()을 호출하고 Release()finally 내에서 호출하여, 허용된 세그먼트 수만 한 번에 진행 중인 상태로 유지됩니다.

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

WaitAsync() 호출은 슬롯이 해제될 때까지 차단되고, 내부 finally에서 엔진을 반환하는 것은 사전 로드된 엔진을 다음 대기 중인 세그먼트로 바로 넘깁니다.

4. 세그먼트별로 OcrInput 폐기

OcrInputusing로 묶어 세그먼트가 읽힌 후, 렌더링 된 페이지 비트맵이 해제되어 다음 작업이 슬롯을 요청하기 전에 해제됩니다.

using에서의 OcrInput은 세그먼트에 걸쳐 비트맵 메모리의 축적을 방지하는 요소입니다; 그렇지 않으면 해제된 슬롯은 여전히 페이지 비트맵을 보유합니다.

Curtis Chau
기술 문서 작성자

커티스 차우는 칼턴 대학교에서 컴퓨터 과학 학사 학위를 취득했으며, Node.js, TypeScript, JavaScript, React를 전문으로 하는 프론트엔드 개발자입니다. 직관적이고 미적으로 뛰어난 사용자 인터페이스를 만드는 데 열정을 가진 그는 최신 프레임워크를 활용하고, 잘 구성되고 시각적으로 매력적인 매뉴얼을 제작하는 것을 즐깁니다.

커티스는 개발 분야 외에도 사물 인터넷(IoT)에 깊은 관심을 가지고 있으며, 하드웨어와 소프트웨어를 통합하는 혁신적인 방법을 연구합니다. 여가 시간에는 게임을 즐기거나 디스코드 봇을 만들면서 기술에 대한 애정과 창의성을 결합합니다.

시작할 준비 되셨나요?
Nuget 다운로드 6,136,090 | 버전: 2026.7 방금 출시
Still Scrolling Icon

아직도 스크롤하고 계신가요?

빠른 증거를 원하시나요? PM > Install-Package IronOcr
샘플을 실행하세요 이미지가 검색 가능한 텍스트로 바뀌는 것을 확인해 보세요.