Hautes Utilisations de Mémoire lors de l'OCR en Bloc
Exécuter l'OCR sur plusieurs segments PDF à la fois multiplie l'utilisation de la mémoire. Chaque tâche rend des bitmaps pleine-page via un OcrInput, et un moteur IronTesseract frais par segment recharge les fichiers modèle de langue à chaque fois. À pleine concurrence de processeur, cela pousse le pic de mémoire dans la gamme des multi-GB, avec des pointes qui échouent dans des environnements à mémoire limitée.
L'OCR est naturellement gourmand en mémoire. Tout OcrInput rend des bitmaps pleine-page, et tout moteur IronTesseract charge les fichiers modèle de langue en mémoire. Créer un nouveau moteur par segment recharge ces modèles de manière répétée, et exécuter une tâche OCR par cœur de CPU (Environment.ProcessorCount) permet à de nombreux travaux lourds en bitmap de s'exécuter côte à côte. Rien ne limitant le nombre de tâches actives, le pic de mémoire évolue directement avec la concurrence.
La solution consiste à limiter le nombre de tâches en cours : restreindre la concurrence, réutiliser les moteurs à partir d'un pool, et réguler le travail avec un sémaphore.
Solution
1. Limiter la concurrence OCR
Limitez le nombre de tâches OCR simultanées à un petit plafond. Moins de tâches simultanées signifient moins de bitmaps pleine-page en mémoire à la fois, ce qui réduit directement le pic. Ajustez le plafond à la capacité de la machine.
// 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. Regrouper les moteurs
Créez exactement un moteur IronTesseract par créneau concurrent lors du démarrage et réutilisez-les à travers chaque segment, plutôt que de construire un nouveau moteur et de recharger le modèle de langue à chaque fois.
// 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())
)
Construire le pool une seule fois amortit le coût de chargement du modèle de langue sur l'ensemble de l'exécution au lieu de le payer par segment.
3. Réguler le travail avec un sémaphore
Initialisez un SemaphoreSlim à la limite de concurrence et enveloppez-le dans using. Chaque tâche appelle WaitAsync() avant de commencer et Release() dans un finally, ainsi seul le nombre autorisé de segments est en vol à la fois.
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
L'appel WaitAsync() bloque jusqu'à ce qu'un créneau se libère, et retourner le moteur dans le finally intérieur transmet un moteur préchargé directement au segment suivant en attente.
4. Dispose OcrInput per segment
Enveloppez chaque OcrInput dans un using afin que ses bitmaps page rendus soient libérés dès que le segment est lu, avant que la tâche suivante ne revendique le créneau.
using sur OcrInput est ce qui empêche la mémoire bitmap de s'accumuler à travers les segments; sans cela, les créneaux libérés conservent encore leurs bitmaps de page.

