대량 OCR 중 높은 피크 메모리
한 번에 많은 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)
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())
)
풀을 한 번 구축하면 그 동안의 언어 모델 로드 비용이 대신 세그먼트별로 지불하는 것이 아닌 전체 실행으로 나누어집니다.
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
WaitAsync() 호출은 슬롯이 해제될 때까지 차단되고, 내부 finally에서 엔진을 반환하는 것은 사전 로드된 엔진을 다음 대기 중인 세그먼트로 바로 넘깁니다.
4. 세그먼트별로 OcrInput 폐기
각 OcrInput을 using로 묶어 세그먼트가 읽힌 후, 렌더링 된 페이지 비트맵이 해제되어 다음 작업이 슬롯을 요청하기 전에 해제됩니다.
using에서의 OcrInput은 세그먼트에 걸쳐 비트맵 메모리의 축적을 방지하는 요소입니다; 그렇지 않으면 해제된 슬롯은 여전히 페이지 비트맵을 보유합니다.

