大規模PDFでのReadPdf時のメモリピーク
大きなPDF全体に対してBarcodeReader.ReadPdf()を一度呼び出すことで、要求されたすべてのページが設定されたDPIでスキャン用にレンダリングされます。そのため、ピークメモリは一定ではなく、ページ数と解像度によってスケールします。 248ページ、80MBのファイルを300DPIで処理する際、メモリは3GBから8GBの間で推移し、12GBまでのスパイクがあったため、メモリ制限のある環境ではエラーが発生することに十分な大きさだった。
BarcodeReader.ReadPdf()は渡されたすべてのページを単一の操作で処理します。 一度に大規模で高DPIのドキュメントの全ページ範囲を読み込むと、それらのページがすべてメモリに保持されるため、ページセットの大きさと共にピーク使用量が増加します。 これは読み込みがバッチ化されていることを示しており、IronBarcodeの確認された欠陥ではありません。
解決策
1. 固定ページチャンクで読み込む
文書をchunkSizeはピークメモリを低くしますが、パスを増やすため、環境のメモリ制限に合わせて調整します。
var barcodeMetadata = new List<BarcodeResult>();
var pageChunks = Enumerable.Range(1, pdf.PageCount)
.GroupBy(i => i / 50)
.Select(g => g.ToArray())
.ToList();
foreach (var chunk in pageChunks)
{
var readerOptions = new PdfBarcodeReaderOptions
{
ExpectMultipleBarcodes = true,
ExpectBarcodeTypes = IronBarCode.BarcodeEncoding.AllOneDimensional,
Speed = ReadingSpeed.Detailed,
ScanMode = BarcodeScanMode.OnlyDetectionModel,
DPI = 300,
PageNumbers = chunk
};
Console.WriteLine($"Processing pages: Pages {chunk.First()}-{chunk.Last()}");
var chunkResult = BarcodeReader.ReadPdf(pdf.Stream, readerOptions);
if (chunkResult != null && chunkResult.Count > 0)
{
barcodeMetadata.AddRange(chunkResult);
}
// Optional memory pressure relief
GC.Collect();
GC.WaitForPendingFinalizers();
}
Console.WriteLine($"Total barcodes found: {barcodeMetadata.Count}");
var barcodeMetadata = new List<BarcodeResult>();
var pageChunks = Enumerable.Range(1, pdf.PageCount)
.GroupBy(i => i / 50)
.Select(g => g.ToArray())
.ToList();
foreach (var chunk in pageChunks)
{
var readerOptions = new PdfBarcodeReaderOptions
{
ExpectMultipleBarcodes = true,
ExpectBarcodeTypes = IronBarCode.BarcodeEncoding.AllOneDimensional,
Speed = ReadingSpeed.Detailed,
ScanMode = BarcodeScanMode.OnlyDetectionModel,
DPI = 300,
PageNumbers = chunk
};
Console.WriteLine($"Processing pages: Pages {chunk.First()}-{chunk.Last()}");
var chunkResult = BarcodeReader.ReadPdf(pdf.Stream, readerOptions);
if (chunkResult != null && chunkResult.Count > 0)
{
barcodeMetadata.AddRange(chunkResult);
}
// Optional memory pressure relief
GC.Collect();
GC.WaitForPendingFinalizers();
}
Console.WriteLine($"Total barcodes found: {barcodeMetadata.Count}");
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports IronBarCode
Dim barcodeMetadata As New List(Of BarcodeResult)()
Dim pageChunks = Enumerable.Range(1, pdf.PageCount) _
.GroupBy(Function(i) i \ 50) _
.Select(Function(g) g.ToArray()) _
.ToList()
For Each chunk In pageChunks
Dim readerOptions As New PdfBarcodeReaderOptions With {
.ExpectMultipleBarcodes = True,
.ExpectBarcodeTypes = BarcodeEncoding.AllOneDimensional,
.Speed = ReadingSpeed.Detailed,
.ScanMode = BarcodeScanMode.OnlyDetectionModel,
.DPI = 300,
.PageNumbers = chunk
}
Console.WriteLine($"Processing pages: Pages {chunk.First()}-{chunk.Last()}")
Dim chunkResult = BarcodeReader.ReadPdf(pdf.Stream, readerOptions)
If chunkResult IsNot Nothing AndAlso chunkResult.Count > 0 Then
barcodeMetadata.AddRange(chunkResult)
End If
' Optional memory pressure relief
GC.Collect()
GC.WaitForPendingFinalizers()
Next
Console.WriteLine($"Total barcodes found: {barcodeMetadata.Count}")
各イテレーションはPageNumbersで名前付けされたページのみを読み取るので、メモリはファイル全体にわたって蓄積される代わりに、チャンク間で減少します。
2. PDFストリームをキャッシュしてその位置をリセットする
ループの前にストリームをローカルに読み取り、それをチャンクごとに巻き戻します。pdfStream.Seek(0, SeekOrigin.Begin)を呼び出します。
3. リーダーオプションを一度だけ構築する
ループ外でPageNumbersのみを変更することで、各イテレーションごとに新しいオプションオブジェクトを割り当てるのを避けます。
4. 結果リストの事前サイズ設定
結果が追加されると内部の配列が繰り返し倍増するのを避けるため、推定容量で結果リストを初期化します。 再現は、開始の目安としておおよそ10ページごとに1つのバーコードを推定します。

