Wysokie zużycie pamięci podczas masowej operacji OCR
Wykonywanie OCR na wielu segmentach PDF naraz zwielokrotnia zużycie pamięci. Każde zadanie renderuje pełnostronicowe mapy bitowe przez OcrInput, a nowy silnik IronTesseract na segment przeładowuje pliki modelu języka za każdym razem. Przy pełnej konkurencji procesorów powoduje to wzrost szczytowego zużycia pamięci do zakresu wielo-gigabajtowego, z przyrostami, które zawodzą w środowiskach z ograniczoną pamięcią.
OCR zużywa dużo pamięci z natury. Każdy OcrInput renderuje pełnostronicowe mapy bitowe, a każdy silnik IronTesseract ładuje pliki modelu języka do pamięci. Tworzenie nowego silnika dla kazdego przeladowania segmentu powoduje wielokrotne ladowanie tych modeli, a uruchamianie jednego zadania OCR na rdzen procesora (Environment.ProcessorCount) pozwala na uruchamianie wielu zadan z obszernymi bitmapami rownolegle. Bez ograniczen co do liczby aktywnych zadan, szczytowe uzycie pamieci rosnie bezposrednio wraz z rownoleglym przetwarzaniem.
Rozwiazanie polega na ograniczeniu liczby zadan w locie: ogranicz przetwarzanie rownolegle, ponownie wykorzystaj silniki z puli i kontroluj prace przy pomocy semafora.
Rozwiązanie
1. Ogranicz przetwarzanie OCR
Ogranicz liczbe jednoczesnych zadan OCR do niewielkiego poziomu. Mniej jednoczesnych zadan oznacza mniej bitmap pelnych stron w pamieci naraz, co bezposrednio obniza szczytowe uzycie pamieci. Dopasuj limit do mozliwosci maszyny.
// 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. Pula silnikow
Utworz dokladnie jeden silnik IronTesseract na kazdy rownolegly slot podczas uruchamiania i wykorzystaj je ponownie w kazdym segmencie, zamiast konstruowac nowy silnik i przeladowywac model jezykowy za kazdym razem.
// 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())
)
Zbudowanie puli raz amortyzuje koszt przeladowania modelu jezykowego podczas calego uruchomienia zamiast placic za to przy kazdym segmencie.
3. Kontroluj prace przy pomocy semafora
Zainicjalizuj SemaphoreSlim do limitu rownoleglego przetwarzania i obejmij go using. Kazde zadanie wywoluje WaitAsync() zanim sie rozpocznie i Release() w finally, wiec tylko dozwolona liczba segmentow jest naraz uruchamiana.
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
Wywolanie WaitAsync() blokuje, az zwolni sie slot, a zwrocenie silnika w wewnetrznym finally przekazuje w pelni przeladowany silnik bezposrednio do nastepnego oczekujacego segmentu.
4. Usuwanie OcrInput na segment
Obejmij kazdy OcrInput w using, aby jego bitmapy strony zostaly zwolnione w momencie odczytu segmentu, zanim nastepne zadanie zajmie slot.
using na OcrInput zapobiega gromadzeniu sie pamieci bitmap w segmentach; bez tego, zwolnione sloty nadal przechowuja bitmapy swoich stron.

