# Alta Memória de Pico Durante OCR em Massa
Executar OCR em muitos segmentos de PDF de uma vez multiplica o uso de memória. Cada tarefa renderiza bitmaps de página inteira através de um `OcrInput`, e um motor `IronTesseract` novo por segmento recarrega os arquivos do modelo de linguagem toda vez. Na plena concorrência do processador isso eleva a memória de pico para a faixa de vários GB, com picos que falham em ambientes com memória limitada.
O OCR é naturalmente pesado em memória. Cada `OcrInput` renderiza bitmaps de página inteira, e cada motor `IronTesseract` carrega arquivos do modelo de linguagem na memória. Criar um novo motor por segmento recarrega esses modelos repetidamente, e executar uma tarefa de OCR por núcleo de CPU (`Environment.ProcessorCount`) permite que muitos trabalhos pesados em bitmap sejam executados lado a lado. Sem nada limitando quantas tarefas estão ativas, a memória de pico escala diretamente com a concorrência.
A solução é limitar o número de tarefas em andamento: limitar a concorrência, reutilizar motores de um pool e organizar o trabalho com um semáforo.
## Solução
### 1. Limitar a concorrência do OCR
Limite o número de tarefas de OCR simultâneas a um teto pequeno. Menos tarefas simultâneas significam menos bitmaps de página inteira na memória de uma vez, o que reduz diretamente o pico. Ajuste o teto à capacidade da máquina.
```csharp
// Clamp concurrency to avoid memory saturation and CPU over-subscription.
int concurrency = Math.Clamp(Environment.ProcessorCount / 2, 1, 4);
```
### 2. Pool dos motores
Crie exatamente um motor `IronTesseract` por slot concorrente na inicialização e reutilize-os em todos os segmentos, em vez de construir um novo motor e recarregar o modelo de linguagem cada vez.
```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())
);
```
Construir o pool uma vez amortiza o custo de carregar o modelo de linguagem em toda a execução em vez de pagar por isso por segmento.
### 3. Organizem o trabalho com um semáforo
Inicialize um `SemaphoreSlim` para o limite de concorrência e envolva-o em `using`. Cada tarefa chama `WaitAsync()` antes de começar e `Release()` em um `finally`, portanto, apenas o número permitido de segmentos está em andamento de uma vez.
```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();
}
```
A chamada `WaitAsync()` bloqueia até que um slot seja liberado, e o retorno do motor no `finally` interno entrega um motor pré-carregado diretamente para o próximo segmento aguardando.
### 4. Disponibilizar OcrInput por segmento
Envolva cada `OcrInput` em `using` para que seus bitmaps de página renderizados sejam liberados no momento em que o segmento é lido, antes que a próxima tarefa ocupe o slot.
[[t:(O `using` em `OcrInput` é o que impede a acumulação de memória de bitmap entre segmentos; sem isso, slots liberados ainda conteriam seus bitmaps de página.)]]
Executar OCR em muitos segmentos de PDF de uma vez multiplica o uso de memória. Cada tarefa renderiza bitmaps de página inteira através de um OcrInput, e um motor IronTesseract novo por segmento recarrega os arquivos do modelo de linguagem toda vez. Na plena concorrência do processador isso eleva a memória de pico para a faixa de vários GB, com picos que falham em ambientes com memória limitada.
O OCR é naturalmente pesado em memória. Cada OcrInput renderiza bitmaps de página inteira, e cada motor IronTesseract carrega arquivos do modelo de linguagem na memória. Criar um novo motor por segmento recarrega esses modelos repetidamente, e executar uma tarefa de OCR por núcleo de CPU (Environment.ProcessorCount) permite que muitos trabalhos pesados em bitmap sejam executados lado a lado. Sem nada limitando quantas tarefas estão ativas, a memória de pico escala diretamente com a concorrência.
A solução é limitar o número de tarefas em andamento: limitar a concorrência, reutilizar motores de um pool e organizar o trabalho com um semáforo.
Solução
1. Limitar a concorrência do OCR
Limite o número de tarefas de OCR simultâneas a um teto pequeno. Menos tarefas simultâneas significam menos bitmaps de página inteira na memória de uma vez, o que reduz diretamente o pico. Ajuste o teto à capacidade da máquina.
// 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. Pool dos motores
Crie exatamente um motor IronTesseract por slot concorrente na inicialização e reutilize-os em todos os segmentos, em vez de construir um novo motor e recarregar o modelo de linguagem cada vez.
// 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#
Construir o pool uma vez amortiza o custo de carregar o modelo de linguagem em toda a execução em vez de pagar por isso por segmento.
3. Organizem o trabalho com um semáforo
Inicialize um SemaphoreSlim para o limite de concorrência e envolva-o em using. Cada tarefa chama WaitAsync() antes de começar e Release() em um finally, portanto, apenas o número permitido de segmentos está em andamento de uma vez.
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#
A chamada WaitAsync() bloqueia até que um slot seja liberado, e o retorno do motor no finally interno entrega um motor pré-carregado diretamente para o próximo segmento aguardando.
4. Disponibilizar OcrInput por segmento
Envolva cada OcrInput em using para que seus bitmaps de página renderizados sejam liberados no momento em que o segmento é lido, antes que a próxima tarefa ocupe o slot.
Pontas: O using em OcrInput é o que impede a acumulação de memória de bitmap entre segmentos; sem isso, slots liberados ainda conteriam seus bitmaps de página.
Curtis Chau é bacharel em Ciência da Computação (Universidade Carleton) e se especializa em desenvolvimento front-end, com experiência em Node.js, TypeScript, JavaScript e React. Apaixonado por criar interfaces de usuário intuitivas e esteticamente agradáveis, Curtis gosta de trabalhar com frameworks modernos e criar manuais bem estruturados e visualmente atraentes.