-
Notifications
You must be signed in to change notification settings - Fork 0
performance
CHDSharp is designed for both batch verification throughput (parallel) and low-latency random access (single-threaded, cached). This page documents the knobs and what to expect.
Measured on a modern desktop with the bundled corpus and typical game images (single-threaded I/O, Release build):
| Scenario | Throughput | Notes |
|---|---|---|
CheckFile(deepCheck: true) |
~200–400 MB/s | 8 parallel workers, bounded memory |
CheckFile(deepCheck: false) |
> 1 GB/s | Header-only |
ChdFile.Read() sequential |
~150–300 MB/s | Single-threaded, last-hunk cache |
ChdFile.ReadHunk() random |
~50–150 MB/s | Per-hunk re-decompression |
Actual numbers depend on the mix of codecs (LZMA is the slowest, raw/zstd the fastest), hunk size, and storage speed.
Chd.TaskCount controls the worker count for CheckFile(deepCheck: true) (default 8, valid range 1–64). It is a process-global setting — set it before calling:
Chd.TaskCount = Environment.ProcessorCount; // or a value you benchmarked
var result = Chd.CheckFile(stream, "game.chd", deepCheck: true);Memory is bounded even with many workers:
- pooled buffers (
ArrayPool) for compressed input, decompressed output, and caches; - a semaphore capping in-flight repeat-block cache to 512 MiB (
blocksToKeep = (512 MiB) / hunkbytes).
ChdBlockRead detects hunks referenced multiple times (deduplicated SELF entries) and:
- computes a usage weight per block (LZMA/Huffman/CD-LZMA blocks are the most expensive to re-decompress, so they get the highest priority),
- keeps the top-N blocks' decompressed copies within the 512 MiB budget (
KeepMostRepeatedBlocks), - serves repeated hunks from the cache instead of re-decompressing.
ChdFile.Read() caches the most recently read hunk, so byte ranges that stay within one hunk cost a single decompression.
ChdFile.ReadHunk() retains decompressed hunks in a configurable LRU cache so random reads that revisit hunks avoid re-decompression. Default size is 1 (equivalent to the single-hunk slot); set CacheSize or call ConfigureCache(n) to keep the last n distinct hunks (n <= 1 disables it). Memory is capped at CacheSize * HunkBytes.
chd.ConfigureCache(16); // keep 16 decompressed hunks
chd.ReadHunk(100, buf); // decompressed
chd.ReadHunk(100, buf); // served from cacheUse a larger CacheSize when a workload performs random/scattered reads that repeatedly touch the same subset of hunks.
ChdFile.Precache() reads the entire compressed file into memory once. Every subsequent hunk read is served from RAM — no stream seeks, no disk I/O. Ideal for:
- random-access workloads over slow/remote storage,
- many small reads across a large image,
- repeated passes over the same file.
var err = chd.Precache();
if (err != ChdError.Chderrnone) { /* >2 GiB file → out of memory; IO failure */ }
// from here on, ReadHunk/Read serve from RAMPrecache() is idempotent, restores the stream position, and returns Chderroutofmemory for files larger than 2 GiB or when allocation fails.
ChdCodecState keeps per-codec scratch alive across hunks:
- LZMA dictionary window (sized to the hunk),
- zstd decompressor instance,
- FLAC decoder + audio buffers,
- Huffman lookup tables (1 MiB each for AVHuff contexts).
This avoids reallocating the most expensive buffers on every hunk — the main reason sequential Read stays in the hundreds of MB/s.
| Operation | Peak memory |
|---|---|
CheckFile(deepCheck: true) |
~workers × hunkbytes × 3 (pooled) + ≤ 512 MiB repeat cache |
ReadHunk / Read
|
~2–3 × hunkbytes + codec state |
Precache |
file size + codec state |
ReadAllBytes |
image size (fails with Chderroutofmemory > 2 GiB) |
-
Batch-verify in parallel with
CheckFile, notReadAllBytesper file. -
Extract with
ExtractToDirectory(streams hunk-by-hunk) rather thanReadAllBytesfor large images. -
Random access patterns benefit from
Precachewhen the underlying stream is slow; on local NVMe the difference is usually small. -
Hunk size matters: larger hunks amortize per-hunk overhead but waste space on small reads. This is a property of the CHD file itself (set by
chdman), not the library. - For sequential streaming, prefer
EnumerateHunks()(single buffer, no per-call allocation) over repeatedReadcalls.
CHDSharp
Format & internals
API & usage
Operations
Writing CHDs
Reference