Altos picos de memoria durante OCR masivo

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

Ejecutar OCR sobre muchos segmentos de PDF a la vez multiplica el uso de memoria. Cada tarea renderiza mapas de bits de página completa a través de un OcrInput, y un motor IronTesseract fresco por segmento recarga los archivos del modelo de lenguaje cada vez. Con la concurrencia total del procesador, esto empuja la memoria máxima al rango de multi-GB, con picos que fallan en entornos con memoria limitada.

OCR es intensivo en memoria por naturaleza. Cada OcrInput renderiza mapas de bits de página completa, y cada motor IronTesseract carga archivos del modelo de lenguaje en la memoria. Crear un nuevo motor por segmento recarga esos modelos repetidamente, y ejecutar una tarea de OCR por núcleo de CPU (Environment.ProcessorCount) permite que muchos trabajos pesados en mapas de bits se ejecuten en paralelo. Sin nada que limite cuántas tareas están activas, la memoria máxima se escala directamente con la concurrencia.

La solución es limitar el número de trabajos en tránsito: limitar la concurrencia, reutilizar motores de un recurso y administrar el trabajo con un semáforo.

Solución

1. Limite la concurrencia de OCR

Limite el número de tareas OCR simultáneas a un techo pequeño. Menos tareas concurrentes significan menos mapas de bits de página completa en la memoria al mismo tiempo, lo que reduce directamente el pico. Ajuste el techo a la capacidad de la 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);
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. Agrupe los motores

Cree exactamente un motor IronTesseract por espacio concurrente al inicio y reutilícelos en todos los segmentos, en lugar de construir un nuevo motor y recargar el modelo de lenguaje 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())
);
' 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

Construir el grupo una vez amortiza el costo de carga del modelo de lenguaje durante toda la ejecución en lugar de pagarlo por segmento.

3. Administre el trabajo con un semáforo

Inicialice un SemaphoreSlim al límite de concurrencia y envuélvalo en using. Cada tarea llama WaitAsync() antes de comenzar y Release() en un finally, para que solo el número permitido de segmentos esté en vuelo a la 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();
}
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

La llamada WaitAsync() se bloquea hasta que un espacio se libera, y devolver el motor en el finally interno entrega un motor precargado directamente al siguiente segmento en espera.

4. Dispose OcrInput per segment

Envuelva cada OcrInput en using para que sus mapas de bits de página renderizados se liberen en el momento en que el segmento se lea, antes de que la siguiente tarea reclame el espacio.

ConsejosEl using en OcrInput es lo que evita que la memoria de mapas de bits se acumule a través de los segmentos; sin él, los espacios liberados aún retendrían sus mapas de bits de página.

Curtis Chau
Escritor Técnico

Curtis Chau tiene una licenciatura en Ciencias de la Computación (Carleton University) y se especializa en el desarrollo front-end con experiencia en Node.js, TypeScript, JavaScript y React. Apasionado por crear interfaces de usuario intuitivas y estéticamente agradables, disfruta trabajando con frameworks modernos y creando manuales bien ...

Leer más
¿Listo para empezar?
Nuget Descargas 6,136,090 | Versión: 2026.7 recién lanzado
Still Scrolling Icon

¿Aún desplazándote?

¿Quieres una prueba rápida? PM > Install-Package IronOcr
ejecuta una muestra y observa cómo tu imagen se convierte en texto buscable.