Fix HDD seek storm: sequential I/O + parallel crypto #5

Merged
zecoxao merged 1 commit from fix-hdd-seek-storm into master 2026-09-15 16:36:14 -05:00
zecoxao commented 2026-09-15 16:35:20 -05:00 (Migrated from github.com)

Summary

Makes ps4encdec use all CPU cores for AES-XTS while keeping disk I/O sequential — important because the target images (e.g. a ~420 GB C:\hdd\12.img) live on mechanical drives.

Design: sequential streaming + parallel crypto.

  • A single reader pulls the file in large sequential 64 MiB chunks; a single writer writes them back in order. The disk only ever sees long sequential runs.
  • Each chunk's sectors are decrypted with an OpenMP parallel for. The aes_xts context is initialized once and only read while crypting, so it is shared read-only across threads (the AES tables are static const).
  • Memory is bounded to one chunk, regardless of thread count.
  • Thread count honors OMP_NUM_THREADS or an optional [threads] argument:
    ps4encdec <keys> <input> <output> <ivoffset> [threads]
  • Makefile gains -fopenmp.

A naive first cut had each thread seek to its own region of the file. On an HDD that turns one sequential stream into a random-seek storm across every thread and throughput collapses — a 420 GB image effectively never finishes. This version avoids that: I/O stays sequential, only the CPU-bound crypto is parallel.

Latent bugs fixed along the way

  • >4 GiB overflow: the original used 32-bit unsigned long + ftell for the size/offset, which wraps on files >4 GiB (exactly the 420 GB case). Streaming with a running 64-bit sector counter removes the whole 32-bit offset path.
  • Garbage tail: the original wrote a full buffer of stale data for a partial final block; now only the bytes actually read are written, so output size equals input size.
  • Keys are read once instead of on every iteration.

Verification (MinGW gcc 13, -O2 -fopenmp)

  • Bit-identical to the previous single-threaded output on aligned input (md5 match).
  • Deterministic across 1/4/8/16 threads.
  • Full-size, correct output on >4 GiB inputs, both 1 MiB-aligned and with a partial tail.
  • Confirmed size detection and end-of-file reads against the real 451,100,540,928-byte 12.img.

🤖 Generated with Claude Code

## Summary Makes `ps4encdec` use all CPU cores for AES-XTS while keeping disk I/O sequential — important because the target images (e.g. a ~420 GB `C:\hdd\12.img`) live on mechanical drives. **Design: sequential streaming + parallel crypto.** - A single reader pulls the file in large sequential 64 MiB chunks; a single writer writes them back in order. The disk only ever sees long sequential runs. - Each chunk's sectors are decrypted with an OpenMP `parallel for`. The `aes_xts` context is initialized once and only read while crypting, so it is shared read-only across threads (the AES tables are `static const`). - Memory is bounded to one chunk, regardless of thread count. - Thread count honors `OMP_NUM_THREADS` or an optional `[threads]` argument: `ps4encdec <keys> <input> <output> <ivoffset> [threads]` - `Makefile` gains `-fopenmp`. > A naive first cut had each thread seek to its own region of the file. On an HDD that turns one sequential stream into a random-seek storm across every thread and throughput collapses — a 420 GB image effectively never finishes. This version avoids that: I/O stays sequential, only the CPU-bound crypto is parallel. ## Latent bugs fixed along the way - **>4 GiB overflow:** the original used 32-bit `unsigned long` + `ftell` for the size/offset, which wraps on files >4 GiB (exactly the 420 GB case). Streaming with a running 64-bit sector counter removes the whole 32-bit offset path. - **Garbage tail:** the original wrote a full buffer of stale data for a partial final block; now only the bytes actually read are written, so output size equals input size. - Keys are read once instead of on every iteration. ## Verification (MinGW gcc 13, `-O2 -fopenmp`) - **Bit-identical** to the previous single-threaded output on aligned input (md5 match). - **Deterministic** across 1/4/8/16 threads. - Full-size, correct output on >4 GiB inputs, both 1 MiB-aligned and with a partial tail. - Confirmed size detection and end-of-file reads against the real 451,100,540,928-byte `12.img`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No description provided.