Sharded image read cache and parallel entropy: faster concurrent reads for IPED workers#3
Open
T0k1To wants to merge 3 commits into
Open
Sharded image read cache and parallel entropy: faster concurrent reads for IPED workers#3T0k1To wants to merge 3 commits into
T0k1To wants to merge 3 commits into
Conversation
…aders
Restructure the TSK_IMG_INFO read cache from a single global cache_lock
into 8 independent banks of 4 entries (total capacity unchanged:
32 x 64KB = 2MB), each bank with its own lock:
- Cache blocks are 64KB-aligned and the address space maps to banks by
256KB regions, so every block belongs to exactly one bank and lookups
scan only that bank's 4 entries (the old code scanned all 32 on every
tsk_img_read call).
- cache_lock keeps its role as the backend lock: image format backends
(raw, AFF, EWF, logical, pool) hold shared state (seek positions,
handles) and remain serialized by it. Strict lock order bank->backend
and no thread ever holds two banks, so the design is deadlock-free by
construction.
- Sequential-access detection with a bounded read-ahead of the next
64KB block, tracked per bank; disable at runtime with
TSK_IMG_READAHEAD=0.
- Lock setup centralized in tsk_img_lock_init()/tsk_img_lock_deinit(),
used by img_open and the APFS/LVM pool wrappers.
- Requests crossing a 64KB block boundary bypass the cache (direct
serialized read, as large reads already did).
Benchmarks on this branch (ext4 corpus, 15k files, warm cache, median
of 3 interleaved A/B runs, 4-core/8-thread laptop):
tsk_img_read 4KB 1 thread: 4180 -> 4507 MB/s (+8%)
2 threads: 3122 -> 3939 MB/s (+26%)
4 threads: 3060 -> 3651 MB/s (+19%)
8 threads: 2575 -> 3436 MB/s (+33%)
Validation: tsk_loaddb database byte-identical to the unpatched build
(154,616 dump statements); fls -r listing identical (15,379 lines);
icat output bit-identical on sampled inodes; fs_thread_test with 8
threads produces identical per-thread logs; ThreadSanitizer clean on
fs_thread_test and an 8-thread read microbenchmark.
This directly benefits IPED's parallel in-process image reading
(ReadContentInputStream via JNI from multiple worker threads).
calculateEntropy() reads up to 99 x 64KB blocks and counts byte frequencies sequentially. Count the blocks with a small thread pool (<= 4 threads, block-stride partitioning), leveraging the sharded image cache for concurrent reads: - Per-block histograms are merged in block order, stopping at the first failed read, replicating the sequential semantics exactly: the result is bit-identical to the sequential pass. - Sequential path kept for Windows, small block counts, thread creation failure, or TSK_ENTROPY_THREADS=0. - The sequential path buffer stays heap-allocated as upstream changed it in 4.15. Measured ~3.4x faster on ext4 (2.28s -> 0.67s over 200 iterations), ~2.2-2.6x on FAT32/ISO9660. ThreadSanitizer clean. Entropy output verified identical to the unpatched build.
The RSA reference MD5 implementation (1991) copies and fills memory with byte-by-byte for loops. Use libc memcpy()/memset() so the compiler can inline and vectorize. NIST vectors pass, including irregular chunking that exercises partial buffering.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR ports a set of performance optimizations to the
4.15.0_iped_patchbranch, targeting IPED's hottest native path: concurrent file-content reads throughlibtsk_jni(ReadContentInputStream) from multiple worker threads.TSK_IMG_INFOread cache becomes 8 independent banks of 4 entries (same total capacity: 32 x 64KB), each with its own lock. Blocks are 64KB-aligned and mapped to banks by 256KB regions, so lookups scan 4 entries instead of 32, and readers of different regions no longer contend on a single global lock.cache_lockkeeps its role as the backend lock (raw/AFF/EWF/logical/pool state stays serialized, no backend changes). Strict bank -> backend lock order and no thread ever takes two banks, so the design is deadlock-free by construction. Includes a per-bank sequential read-ahead (disable withTSK_IMG_READAHEAD=0; the effect on NVMe is neutral, the expected benefit is on slow media such as USB drives, HDDs and network shares).calculateEntropy()counts its ~99 blocks of 64KB with up to 4 threads (per-block histograms merged in block order, so the result is bit-identical to the sequential pass;TSK_ENTROPY_THREADS=0disables). About 3.4x faster on volumes that trigger encryption detection.MD5_memcpy/MD5_memset) with libcmemcpy/memset.Benchmarks (this branch vs unmodified 4.15.0_iped_patch)
tsk_img_read4KB reads, ext4 corpus (15k files), warm cache, median of 3 interleaved A/B runs, 4-core/8-thread machine:End-to-end through the Java bindings (the exact IPED read path:
SleuthkitCase+AddImageProcess+ 8 threads draining every file viaReadContentInputStream, 16,127 files / 1.5GB): median 1993 -> 2309 MB/s (+16%, steady-state passes +14% to +32%), 0 read errors.Correctness validation
tsk_loaddbcase database byte-identical to the unpatched build (154,616 dump statements compared).fls -rlisting identical (15,379 lines);icatbit-identical on sampled inodes; entropy output identical.fs_thread_testwith 8 threads: identical per-thread logs.make checkPASS; no new compiler warnings (-Wall -Wextra).Why this matters for IPED, and where the gains show up
All gains are on the image read path; parsing, indexing and Java-side hashing CPU time are unaffected.
robustImageReading = false): every worker thread reads file content throughSleuthkitInputStream/JNI concurrently. This is where the +16% to +33% aggregate read throughput applies directly (hashing, signature detection, parsing, export, thumbnails).robustImageReading = true: eachSleuthkitServerhelper process is a single-threaded libtsk consumer, so today the benefit there is in the single-thread class (+8% and read-ahead, per server). The full multi-thread win becomes reachable once IPED can raisenumImageReadersor prefer in-process reads when a patched TSK is detected. The poor concurrency of the single-lock cache was one of the original reasons for theceil(cores/6)cap, and this PR removes that constraint. A follow-up IPED-side PR is planned.AddImageProcess, single-threaded by design): +8% from the cheaper cache lookup, plus read-ahead on sequential metadata sweeps.If accepted, a version bump of the published artifacts (e.g.
p2) would let IPED conditionnumImageReaders/in-process reading on the presence of the sharded cache.