# 대량 OCR 중 높은 피크 메모리
한 번에 많은 PDF 세그먼트에 대해 OCR을 실행하면 메모리 사용량이 증가합니다. 각 작업은 `OcrInput`를 통해 전체 페이지 비트맵을 렌더링하며, 세그먼트당 새로운 `IronTesseract` 엔진이 매번 언어 모델 파일을 새로 로드합니다. 프로세서 동시도가 최대일 때 이로 인해 메모리 제한이 있는 환경에서 실패할 수 있는 멀티 GB 범위의 메모리 피크가 발생합니다.
OCR은 본질적으로 메모리 사용이 많은 작업입니다. 모든 `OcrInput`은 전체 페이지 비트맵을 렌더링하고, 모든 `IronTesseract` 엔진은 언어 모델 파일을 메모리에 로드합니다. 세그먼트마다 새로운 엔진을 생성하면 이러한 모델이 반복 로드되고, CPU 코어당 OCR 작업을 실행 (`Environment.ProcessorCount`)하면 많은 비트맵 중심의 작업이 나란히 실행됩니다. 활성화된 작업 수를 제한하는 것이 없으면 피크 메모리는 동시성과 함께 직접 확장됩니다.
수정 방법은 진행 중인 작업의 수를 제한하는 것입니다: 동시성을 제한하고, 풀에서 엔진을 재사용하고, 세마포어로 작업을 제어합니다.
## 해결책
### 1. OCR 동시성 제한
최대 동시 OCR 작업 수를 작게 제한하십시오. 동시 작업이 적다는 것은 한 번에 메모리에 있는 전체 페이지 비트맵의 수가 적다는 것을 의미하며, 이는 직접적으로 피크를 낮춥니다. 머신의 역량에 맞춰 최대치를 조정하십시오.
```csharp
// Clamp concurrency to avoid memory saturation and CPU over-subscription.
int concurrency = Math.Clamp(Environment.ProcessorCount / 2, 1, 4);
```
### 2. 엔진을 풀링
시작할 때 동시 슬롯당 정확히 하나의 `IronTesseract` 엔진을 생성하고, 매번 새로운 엔진을 생성하고 언어 모델을 재로드하는 대신, 모든 세그먼트에 대해 이를 다시 사용하십시오.
```csharp
// Pre-create one engine per concurrent slot and reuse them across segments.
var enginePool = new ConcurrentBag<IronTesseract>(
Enumerable.Range(0, concurrency).Select(_ => new IronTesseract())
);
```
풀을 한 번 구축하면 그 동안의 언어 모델 로드 비용이 대신 세그먼트별로 지불하는 것이 아닌 전체 실행으로 나누어집니다.
### 3. 세마포어로 작업 제어
동시성 제한에 `SemaphoreSlim`을 초기화하고 이를 `using`로 묶습니다. 각 작업은 시작하기 전에 `WaitAsync()`을 호출하고 `Release()`을 `finally` 내에서 호출하여, 허용된 세그먼트 수만 한 번에 진행 중인 상태로 유지됩니다.
```csharp
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();
}
```
`WaitAsync()` 호출은 슬롯이 해제될 때까지 차단되고, 내부 `finally`에서 엔진을 반환하는 것은 사전 로드된 엔진을 다음 대기 중인 세그먼트로 바로 넘깁니다.
### 4. 세그먼트별로 OcrInput 폐기
각 `OcrInput`을 `using`로 묶어 세그먼트가 읽힌 후, 렌더링 된 페이지 비트맵이 해제되어 다음 작업이 슬롯을 요청하기 전에 해제됩니다.
[[t:(`using`에서의 `OcrInput`은 세그먼트에 걸쳐 비트맵 메모리의 축적을 방지하는 요소입니다; 그렇지 않으면 해제된 슬롯은 여전히 페이지 비트맵을 보유합니다.)]]
한 번에 많은 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);
C#
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())
);
C#
풀을 한 번 구축하면 그 동안의 언어 모델 로드 비용이 대신 세그먼트별로 지불하는 것이 아닌 전체 실행으로 나누어집니다.
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();
}
C#
WaitAsync() 호출은 슬롯이 해제될 때까지 차단되고, 내부 finally에서 엔진을 반환하는 것은 사전 로드된 엔진을 다음 대기 중인 세그먼트로 바로 넘깁니다.
4. 세그먼트별로 OcrInput 폐기
각 OcrInput을 using로 묶어 세그먼트가 읽힌 후, 렌더링 된 페이지 비트맵이 해제되어 다음 작업이 슬롯을 요청하기 전에 해제됩니다.
팁: using에서의 OcrInput은 세그먼트에 걸쳐 비트맵 메모리의 축적을 방지하는 요소입니다; 그렇지 않으면 해제된 슬롯은 여전히 페이지 비트맵을 보유합니다.
커티스 차우는 칼턴 대학교에서 컴퓨터 과학 학사 학위를 취득했으며, Node.js, TypeScript, JavaScript, React를 전문으로 하는 프론트엔드 개발자입니다. 직관적이고 미적으로 뛰어난 사용자 인터페이스를 만드는 데 열정을 가진 그는 최신 프레임워크를 활용하고, 잘 구성되고 시각적으로 매력적인 매뉴얼을 제작하는 것을 즐깁니다.