# Hoher Speicherverbrauch bei Massen-OCR
Das Ausführen von OCR über viele PDF-Segmente zugleich vervielfacht den Speicherverbrauch. Jede Aufgabe rendert vollseitige Bitmaps durch einen `OcrInput`, und eine neue `IronTesseract` Engine pro Segment lädt die Sprachmoduldateien jedes Mal neu. Bei voller Prozessor-Konkurrenz wird der maximale Speicherbedarf in den Multi-GB-Bereich gedrückt, mit Spitzen, die in speicherbeschränkten Umgebungen scheitern.
OCR ist von Natur aus speicherintensiv. Jeder `OcrInput` rendert vollseitige Bitmaps, und jede `IronTesseract` Engine lädt Sprachmoduldateien in den Speicher. Das Erstellen einer neuen Engine pro Segment lädt diese Modelle wiederholt neu, und das Ausführen einer OCR-Aufgabe pro CPU-Kern (`Environment.ProcessorCount`) erlaubt es, viele bitmap-lastige Jobs nebeneinander laufen zu lassen. Da es keine Begrenzung gibt, wie viele Aufgaben aktiv sind, skaliert der maximale Speicherbedarf direkt mit der Konkurrenz.
Die Lösung besteht darin, die Anzahl der gerade in Arbeit befindlichen Jobs zu begrenzen: Begrenzen Sie die Konkurrenz, wiederverwendbare Motoren aus einem Pool verwenden und Arbeiten mit einem Semaphor steuern.
## Lösung
### 1. OCR-Konkurrenz begrenzen
Begrenzen Sie die Anzahl der gleichzeitigen OCR-Aufgaben auf eine kleine Obergrenze. Weniger gleichzeitige Aufgaben bedeuten weniger Vollseiten-Bitmaps im Speicher zugleich, was die Spitzenwerte direkt senkt. Passen Sie die Obergrenze an die Leistungsfähigkeit der Maschine an.
```csharp
// Clamp concurrency to avoid memory saturation and CPU over-subscription.
int concurrency = Math.Clamp(Environment.ProcessorCount / 2, 1, 4);
```
### 2. Die Motoren Poolen
Erstellen Sie genau eine `IronTesseract` Engine pro konkurrierendem Slot beim Start und verwenden Sie sie über jedes Segment hinweg erneut, anstatt jedes Mal eine neue Engine zu konstruieren und das Sprachmodell neu zu laden.
```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())
);
```
Das Bauen des Pools einmal amortisiert die Kosten für das Laden des Sprachmoduls über den gesamten Lauf statt pro Segment zu bezahlen.
### 3. Arbeiten mit einem Semaphor steuern
Initialisieren Sie ein `SemaphoreSlim` auf das Konkurrenzlimit und umwickeln Sie es mit `using`. Jede Aufgabe ruft `WaitAsync()` auf, bevor sie startet, und `Release()` in einem `finally` auf, sodass nur die erlaubte Anzahl von Segmenten gleichzeitig aktiv ist.
```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();
}
```
Der `WaitAsync()` Aufruf blockiert, bis ein Slot frei wird, und das Zurückgeben der Engine im inneren `finally` übergibt eine vorab geladene Engine direkt an das nächste wartende Segment.
### 4. Dispose OcrInput per segment
Wickeln Sie jede `OcrInput` in `using` ein, sodass die gerenderten Seiten-Bitmaps freigegeben werden, sobald das Segment gelesen wird, bevor die nächste Aufgabe den Slot beansprucht.
[[t:(Der `using` auf `OcrInput` verhindert, dass sich Bitmap-Speicher über Segmente hinweg ansammelt; ohne es würden freigegebene Slots immer noch ihre Seiten-Bitmaps halten.)]]
Das Ausführen von OCR über viele PDF-Segmente zugleich vervielfacht den Speicherverbrauch. Jede Aufgabe rendert vollseitige Bitmaps durch einen OcrInput, und eine neue IronTesseract Engine pro Segment lädt die Sprachmoduldateien jedes Mal neu. Bei voller Prozessor-Konkurrenz wird der maximale Speicherbedarf in den Multi-GB-Bereich gedrückt, mit Spitzen, die in speicherbeschränkten Umgebungen scheitern.
OCR ist von Natur aus speicherintensiv. Jeder OcrInput rendert vollseitige Bitmaps, und jede IronTesseract Engine lädt Sprachmoduldateien in den Speicher. Das Erstellen einer neuen Engine pro Segment lädt diese Modelle wiederholt neu, und das Ausführen einer OCR-Aufgabe pro CPU-Kern (Environment.ProcessorCount) erlaubt es, viele bitmap-lastige Jobs nebeneinander laufen zu lassen. Da es keine Begrenzung gibt, wie viele Aufgaben aktiv sind, skaliert der maximale Speicherbedarf direkt mit der Konkurrenz.
Die Lösung besteht darin, die Anzahl der gerade in Arbeit befindlichen Jobs zu begrenzen: Begrenzen Sie die Konkurrenz, wiederverwendbare Motoren aus einem Pool verwenden und Arbeiten mit einem Semaphor steuern.
Lösung
1. OCR-Konkurrenz begrenzen
Begrenzen Sie die Anzahl der gleichzeitigen OCR-Aufgaben auf eine kleine Obergrenze. Weniger gleichzeitige Aufgaben bedeuten weniger Vollseiten-Bitmaps im Speicher zugleich, was die Spitzenwerte direkt senkt. Passen Sie die Obergrenze an die Leistungsfähigkeit der Maschine an.
// 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. Die Motoren Poolen
Erstellen Sie genau eine IronTesseract Engine pro konkurrierendem Slot beim Start und verwenden Sie sie über jedes Segment hinweg erneut, anstatt jedes Mal eine neue Engine zu konstruieren und das Sprachmodell neu zu laden.
// 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#
Das Bauen des Pools einmal amortisiert die Kosten für das Laden des Sprachmoduls über den gesamten Lauf statt pro Segment zu bezahlen.
3. Arbeiten mit einem Semaphor steuern
Initialisieren Sie ein SemaphoreSlim auf das Konkurrenzlimit und umwickeln Sie es mit using. Jede Aufgabe ruft WaitAsync() auf, bevor sie startet, und Release() in einem finally auf, sodass nur die erlaubte Anzahl von Segmenten gleichzeitig aktiv ist.
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#
Der WaitAsync() Aufruf blockiert, bis ein Slot frei wird, und das Zurückgeben der Engine im inneren finally übergibt eine vorab geladene Engine direkt an das nächste wartende Segment.
4. Dispose OcrInput per segment
Wickeln Sie jede OcrInput in using ein, sodass die gerenderten Seiten-Bitmaps freigegeben werden, sobald das Segment gelesen wird, bevor die nächste Aufgabe den Slot beansprucht.
Tipps: Der using auf OcrInput verhindert, dass sich Bitmap-Speicher über Segmente hinweg ansammelt; ohne es würden freigegebene Slots immer noch ihre Seiten-Bitmaps halten.
Curtis Chau hat einen Bachelor-Abschluss in Informatik von der Carleton University und ist spezialisiert auf Frontend-Entwicklung mit Expertise in Node.js, TypeScript, JavaScript und React. Leidenschaftlich widmet er sich der Erstellung intuitiver und ästhetisch ansprechender Benutzerschnittstellen und arbeitet gerne mit modernen Frameworks sowie der Erstellung gut strukturierter, optisch ansprechender Handbücher.