最終更新: a few seconds ago Remove Netlify-related code from main (grafted, HEAD)
DICOM ファイル読み込みの並列化
概要
現在の CIRCUS DB/CS では、100 枚そこそこ、データ量にして 100MiB に満たないほどのシリーズを読み込むのに 5 秒以上かかる場合がある。今時の HDD/SDD はシーケンシャルであれば 1 秒に数百 MiB 程度の読み書きは軽くできる性能があるため、パフォーマンスが 1 桁足りないていない。
そこのボトルネックを解消したい。
前調査
132 枚、データ量 67 MiB の MRA シリーズを調査に使用。以下の場合分けで処理速度を計測する。
- 直列に読むか、並列で読むか
- この DICOM シリーズのファイルを置く場所(Azure VM のローカルディスク vs Azure Files を使い NSF でマウントしたディレクトリ)
dicomImageExtractorで画素値を抽出する処理をするかどうか
テストコード
test('read sequentially', async () => {
const extract = dicomImageExtractor();
for (let i = 1; i <= images; i++) {
const content = await fs.readFile(`${path}/${`${i}`.padStart(8, '0')}.dcm`);
const { metadata, pixelData } = extract(content.buffer as ArrayBuffer);
}
});
test('read in parallel', async () => {
const extract = dicomImageExtractor();
const range = new Array(images).fill(0).map((_, i) => i + 1);
const contents = await Promise.all(
range.map(i => {
return (async () => {
const content = await fs.readFile(
`${path}/${`${i}`.padStart(8, '0')}.dcm`
);
return content;
return extract(content.buffer as ArrayBuffer);
})();
})
);
});
結果
VM ローカルディスク + 画素値抽出処理あり
- 直列: 660 ms
- 並列: 496 ms
VM ローカルディスク + 画素抽出処理なし(つまり SSD から 67MiB 分のファイルを読み込むだけ)
- 直接: 142 ms
- 並列: 105 ms
NAS (/var/data/dicom) + 画素値抽出処理あり
- 直列: 8555 ms
- 並列: 2489 ms
NAS (/var/data/dicom) + 画素値抽出処理なし
- 直列: 5992 ms
- 並列: 2080 ms
考察
- ボトルネックは 「NAS のレイテンシ > NAS の転送速度 >> DICOM 画素抽出処理」。
- 少なくとも開発機では Azure Files でマウントした
/var/dataに DICOM 画像を置いていることが遅さの原因の大部分。普通のローカルディスクと比べてパフォーマンス特性が全く異なっている。 - ディスクから DICOM ファイルをできるだけ並列に読み出すことでレイテンシの影響を抑え、数倍の高速化ができる。
- レイテンシの問題は将来的にデータ保存のバックエンドが S3 や GCS ベースになった時に確実により顕在化するはず。
- かといって 1000 枚もあるような DICOM シリーズに対して 1000 ファイルを完全に並列で読み出すのもどうなのか。
- 少なくとも GCS と S3 は毎秒 5000 リクエストの処理が可能と書いてある
- ただし 67 MiB の画素値抽出処理に 500 ms かかっているので、
dicomImageExtractor自体も十分速いとは言いがたい

今の PriorityIntegerLoader は「プライオリティを付けて直列的な読み出しをする」ということをしているが、こんなことを一切せずに並列的に読み出した方がむしろ速い可能性が高い。この辺りを改修していく必要がある。