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);Dim concurrency As Integer = Math.Clamp(Environment.ProcessorCount \ 2, 1, 4)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 = New ConcurrentBag(Of IronTesseract)(
Enumerable.Range(0, concurrency).Select(Function(_) New IronTesseract())
)' Pre-create one engine per concurrent slot and reuse them across segments.
Dim enginePool = 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();
}Using semaphore As New SemaphoreSlim(concurrency)
Await semaphore.WaitAsync()
Try
' Rent a pre-loaded engine from the pool.
Dim ocr As IronTesseract
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
End UsingUsing semaphore As New SemaphoreSlim(concurrency)
Await semaphore.WaitAsync()
Try
' Rent a pre-loaded engine from the pool.
Dim ocr As IronTesseract
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
End UsingWywolanie 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.Curtis Chau posiada tytuł licencjata z informatyki (Uniwersytet Carleton) i specjalizuje się w front-endowym rozwoju, z ekspertką w Node.js, TypeScript, JavaScript i React. Pasjonuje się tworzeniem intuicyjnych i estetycznie przyjemnych interfejsów użytkownika, Curtis cieszy się pracą z nowoczesnymi frameworkami i tworzeniem dobrze zorganizowanych, atrakcyjnych wizualnie podręczników.