IRONSOFTWAREHOME

High Peak Memory During Bulk OCR

Curtis Chau
Curtis Chau
Updated: June 24, 2026

Running OCR over many PDF segments at once multiplies memory use. Each task renders full-page bitmaps through an OcrInput, and a fresh IronTesseract engine per segment reloads the language model files every time. At full processor concurrency this pushes peak memory into the multi-GB range, with spikes that fail in memory-limited environments.

OCR is memory-heavy by nature. Every OcrInput renders full-page bitmaps, and every IronTesseract engine loads language model files into memory. Creating a new engine per segment reloads those models repeatedly, and running one OCR task per CPU core (Environment.ProcessorCount) lets many bitmap-heavy jobs run side by side. With nothing limiting how many tasks are active, peak memory scales directly with concurrency.

The fix is to bound the number of in-flight jobs: cap concurrency, reuse engines from a pool, and gate work with a semaphore.

Solution

1. Cap OCR concurrency

Clamp the number of simultaneous OCR tasks to a small ceiling. Fewer concurrent tasks mean fewer full-page bitmaps in memory at once, which directly lowers the peak. Tune the ceiling to the machine's capability.

// Clamp concurrency to avoid memory saturation and CPU over-subscription.
int concurrency = Math.Clamp(Environment.ProcessorCount / 2, 1, 4);
C#

2. Pool the engines

Create exactly one IronTesseract engine per concurrent slot at startup and reuse them across every segment, rather than constructing a new engine and reloading the language model each time.

// 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#

Building the pool once amortizes the language-model load cost across the whole run instead of paying it per segment.

3. Gate work with a semaphore

Initialize a SemaphoreSlim to the concurrency limit and wrap it in using. Each task calls WaitAsync() before it starts and Release() in a finally, so only the allowed number of segments are ever in flight at once.

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#

The WaitAsync() call blocks until a slot frees up, and returning the engine in the inner finally hands a pre-loaded engine straight to the next waiting segment.

4. Dispose OcrInput per segment

Wrap each OcrInput in using so its rendered page bitmaps are released the moment the segment is read, before the next task claims the slot.

Tips: The using on OcrInput is what keeps bitmap memory from accumulating across segments; without it, freed slots still hold their page bitmaps.

5. Cap parallelism inside each Read

The steps above bound how many segments run at once, but each IronTesseract also reads pages in parallel within a single Read call, defaulting to one page per logical processor. The two limits multiply: four concurrent segments against a default of 16 is 64 native engines, not four. Set MaxDegreeOfParallelism on each pooled engine so the inner layer is bounded too.

// Bound the inner layer as well as the outer one.
// Without this, each of the `concurrency` segments still fans out
// across every logical processor.
foreach (var engine in enginePool)
    engine.MaxDegreeOfParallelism = 2;
C#

See how to control parallel OCR memory usage for how the two properties interact and what each concurrent page costs.

Curtis Chau
Technical Writer

Curtis Chau holds a Bachelor’s degree in Computer Science (Carleton University) and specializes in front-end development with expertise in Node.js, TypeScript, JavaScript, and React. Passionate about crafting intuitive and aesthetically pleasing user interfaces, Curtis enjoys working with modern frameworks and creating well-structured, visually appealing manuals.

...
Read More

Ready to Get Started?

Nuget Downloads 6,236,385Version:2026.9just released

Get your free 30-day Trial Key instantly.
No credit card or account creation required
C# NuGet Library for PDF
Install with NuGet

Version: 2026.9

PM > Install-Package IronOcr
nuget.org/packages/IronOcr/
  1. In Solution Explorer, right-click References, Manage NuGet Packages
  2. Select Browse and search "IronOCR"
  3. Select the package and install
C# PDF DLL
Download DLL

Version: 2026.9

or download Windows Installer here.

  1. Download and unzip IronOCR to a location such as ~/Libs within your Solution directory
  2. In Visual Studio Solution Explorer, right click References. Select Browse, "IronOCR.dll"

Licenses from $999

Key in blue circle

Get your free 30-day Trial Key instantly.

Your trial license will be sent to your email address

No limitations. 100% unlocked. No credit card.

bullet_checkedNo credit card or account creation requiredNo limitations. 100% unlocked. No credit card.
  • Logo Aetna
  • Logo NASA
  • Logo GE
  • Logo Porsche
  • Logo USDA
  • Logo Qatar
Join Millions of Engineers who’ve tried IronPDF
Get Your No-Obligation Consult
Complete the form below or email sales@ironsoftware.com
Your details will always be kept confidential.
Trusted by Millions of Engineers Worldwide
Iron Software's customer logos
Get your free 30-day Trial Key instantly.
No credit card or account creation required