VFS benchmarks: методология и результаты (issue #182)¶
Критерий приёмки № 6 issue #182: «Benchmark native ≤ 20 % slower на
range_read_1m, extract_10m». Этот документ фиксирует воспроизводимую
методологию, фактические результаты и честные выводы. Главное ограничение
проекта: производительность не может оправдать ослабление integrity gates —
все измерения выполнены на неизменном read-only пути, и ни одна проверка
целостности не была отключена ради цифр.
1. Инструментарий¶
benchmarks/bench_vfs.py— harness (schema v2, read-only):- источник открывается только для чтения; провайдер обязан вернуть нативный
backend — если роутинг выбрал pytsk3-fallback,
native-прогон помечается ошибкой, а не засчитывается (test_native_benchmark_does_not_count_pytsk_fallback_as_native); - обязателен явный ограниченный экстент тома (
--offset,--volume-size), выход за границы образа отклоняется; - пути валидируются: абсолютные, нормализованные, без
..; - range-read ограничен 1 МиБ на файл (
range_read_1m); extract — полный файл во временную директорию с проверкой размера; - harness не управляет page cache ОС — состояние кэша фиксируется
меткой (
--cache-state), а не создаётся искусственно; - когда запускаются оба backend'а, их повторы выполняются попарно с
чередованием порядка (
native → pytsk3, затемpytsk3 → native); точная последовательность сохраняется вbackend_execution_order. Это снижает систематический временной/order bias, но не очищает cache и не делает его cold или строго контролируемым; - сравнение backend'ов публикуется только при побайтовом совпадении содержимого (размер + SHA-256 каждого файла против эталонного прогона); при расхождении сравнение производительности скрывается.
- каждый отчёт содержит
capability_contractс версией общей matrix, проверенными операциями и явными значениямиevidence=benchmark_observed_onlyиparity=not_claimed; каждый backend также получает вложенныйcapability_status. Benchmark не превращает измерение скорости или совпадение содержимого в доказательство полной filesystem parity.
Сохранённый отчёт можно проверить перед публикацией:
python scripts/benchmark_artifact.py /tmp/vfs-bench/bench_report.json \
--json-out /tmp/vfs-bench/benchmark-validation.json
Validator требует schema v2, bounded image/workload, полный набор операций,
ровно заявленное число повторов, clean source provenance и консервативные
evidence=benchmark_observed_only/parity=not_claimed. Это проверка качества
артефакта, а не заявление filesystem parity; write_operations всегда false.
2. Фикстура: синтетический ext2-образ¶
Бинарные образы в репозиторий не коммитятся. Детерминированный образ генерируется локально:
python scripts/make_bench_ext2_image.py --output /tmp/bench.ext2
# Optional extended fixture for the acceptance workload:
python scripts/make_bench_ext2_image.py --output /tmp/bench-10m.ext2 --include-10m
Скрипт форматирует образ через mke2fs -t ext2 -b 4096 и наполняет его через
собственный NativeExt2ImageWriter (staged-запись с hash-верификацией и
rollback-снимком), т. е. генерация фикстуры проходит через те же integrity
gates, что и продукт. Содержимое файлов детерминировано (SHA-256 от
seed:counter, псевдослучайные, плохо сжимаемые данные).
Опциональный --include-10m добавляет /bench/large_10m.dat в готовый образ
через debugfs исключительно как test/benchmark fixture setup для reader-а.
Текущий bounded ext2 writer поддерживает double-indirect regular files до
64 MiB; использование debugfs здесь не является обходом production writer
limit и само по себе не проверяет writer parity на 10 MiB. Нужны mke2fs и
debugfs из e2fsprogs.
После staged-записей генератор нормализует только metadata самой fixture:
UUID, directory-hash seed и поля времени superblock/inode. Поэтому два запуска
на одной версии mke2fs дают побайтно идентичный образ; это проверяют
test_benchmark_fixture_generator_is_byte_reproducible и
test_10m_benchmark_fixture_is_reproducible_and_native_readable. Production
writer не меняется: нормализация и debugfs fixture setup находятся только в
benchmark generator.
В стандартной фикстуре самый большой файл — 4 МиБ по выбору benchmark workload;
writer profile допускает большие regular files через bounded double-indirect
addressing. Для критерия extract_10m запустите расширенный режим runner:
INCLUDE_10M=1 scripts/run_vfs_benchmarks.sh /tmp/vfs-bench-10m
Параметры фикстуры (32 МиБ):
| Параметр | Значение |
|---|---|
| Размер образа | 33 554 432 байта (32 МиБ) |
| Файловая система | ext2 (mke2fs, none,filetype,sparse_super,large_file) |
| Размер блока ФС | 4096 байт |
| Offset тома | 0 байт |
| Volume size | 33 554 432 байта |
| mke2fs | e2fsprogs 1.47.0 |
| SHA-256 default fixture | 686da57622d45ad1906337f00d93561fc0be75f12bd222e604a1d10c1a1c1be4 |
| SHA-256 extended 10 MiB fixture | 2eb5a1b64d081d789fc5eb810489f7e2608dafb8015d7430db9489a7d41f6a43 |
Набор файлов (все под /bench, плюс каталог many для list_dir):
| Путь | Размер |
|---|---|
/bench/tiny_1k.dat |
1 КиБ |
/bench/small_16k.dat |
16 КиБ |
/bench/small_64k.dat |
64 КиБ |
/bench/medium_512k.dat |
512 КиБ |
/bench/large_1m.dat |
1 МиБ |
/bench/large_4m.dat |
4 МиБ (максимум нативного writer'а) |
/bench/large_10m.dat |
10 МиБ (только с --include-10m, test-only debugfs fixture) |
/bench/many/file_{000,008,016}.dat |
4 КиБ × 3 |
3. Окружение¶
| Параметр | Значение |
|---|---|
| Python | CPython 3.12.3 |
| Платформа | Linux-6.18.38+-aarch64-with-glibc2.39 |
| pytsk3 | 20260715 (manylinux aarch64 wheel; сборка из исходников не требовалась) |
| Cache state | unspecified; page cache не управлялся harness'ом |
| Методика повтора | 1 разогревочная итерация + 5 измеряемых итераций на backend; каждая итерация открывает и закрывает провайдера заново; open исключён из метрики итерации |
Воспроизведение:
scripts/run_vfs_benchmarks.sh /tmp/vfs-bench
# отчёт: /tmp/vfs-bench/bench_report.json (schema v2, полные выборки)
4. Результаты¶
Два независимых запуска до новой 10 MiB fixture/reader оптимизации сохранены как baseline и не являются текущим результатом. Для всех запусков ниже harness использует один warmup, пять измеряемых итераций на backend и чередует порядок пар. Метрика full iteration — медиана полного времени без открытия провайдера; page cache не контролируется.
4.1 Baseline: исходная 9-файловая фикстура¶
Raw schema-v2 отчёты: run 1 и run 2.
| Прогон | native, медиана итерации | pytsk3, медиана итерации | native/pytsk3 |
|---|---|---|---|
| run 1 | 0.02575 с | 0.01701 с | 1.51× (+51.4 %) |
| run 2 | 0.02924 с | 0.01950 с | 1.50× (+49.9 %) |
В обоих прогонах content_equivalent = true; SHA-256 fixture совпал
(686da57622d45ad1906337f00d93561fc0be75f12bd222e604a1d10c1a1c1be4).
Содержимое native и pytsk3 совпало по размерам и SHA-256 каждого файла.
Оба baseline прогона были content-equivalent и использовали fixture SHA-256
686da57622d45ad1906337f00d93561fc0be75f12bd222e604a1d10c1a1c1be4.
Их результаты фиксируют gap до следующих изменений: native full iteration был
на 49.9–51.4 % медленнее pytsk3.
4.2 Текущий extended benchmark: 10 MiB extraction и range-read optimization¶
Два итоговых прогона выполнены на CPython 3.12.3 / Linux ARM64 с pytsk3 20260715.
Они используют одну и ту же детерминированную 32 MiB fixture с
/bench/large_10m.dat; fixture SHA-256:
2eb5a1b64d081d789fc5eb810489f7e2608dafb8015d7430db9489a7d41f6a43.
Raw schema-v2 отчёты: run 1
и run 2.
| Прогон | Full iteration native / pytsk3 | large_1m.dat range-read native / pytsk3 |
large_10m.dat extract native / pytsk3 |
|---|---|---|---|
| run 1 | 0.04048 с / 0.21686 с (0.187×) | 0.218 мс / 0.322 мс (0.679×) | 10.191 мс / 92.756 мс (0.110×) |
| run 2 | 0.04066 с / 0.21543 с (0.189×) | 0.215 мс / 0.312 мс (0.691×) | 10.594 мс / 92.335 мс (0.115×) |
Оба extended прогона вернули content_equivalent = true; SHA-256 всех файлов
совпал между backend'ами. large_1m.dat range-read — ровно 1 MiB; extraction
полностью записывает и проверяет размер 10 MiB. Хеширование destination
выполняется вне таймера.
4.3 Baseline operation aggregates (9 файлов, до range-read optimization)¶
| Операция | Вызовов/итерацию | native, медиана суммы за итерацию | pytsk3, медиана суммы за итерацию |
|---|---|---|---|
| list_dir | 2 | 0.622 мс | 0.315 мс |
| stat | 9 | 1.937 мс | 1.804 мс |
| read_range | 9 | 3.149 мс | 0.619 мс |
| extract | 9 | 13.282 мс | 7.462 мс |
Операционные времена в baseline таблице — медианы суммы вызовов за одну
итерацию run 1. read_range был ограничен 1 МиБ на файл.
5. Выводы (честные)¶
- Acceptance performance criterion № 6 достигнут на измеренной ARM64
workload: в обоих финальных прогонах 1 MiB range-read и 10 MiB extraction
native были быстрее
pytsk3; результаты приведены в § 4.2. Вывод ограничен этим окружением, этой fixture и uncontrolled OS page cache — его нельзя переносить на другие машины без повторных измерений. - 10 MiB fixture строится отдельным opt-in режимом через
debugfsтолько при подготовке тестового образа. Production writer и read-only benchmark path этим инструментом не записывают данные. - Оптимизации чтения сохраняют integrity gates: direct/single-indirect pointers bulk-unpack'ятся в пределах запрошенного bounded range; contiguous physical runs валидируются целиком по block-group metadata и allocation bitmap bitmask перед чтением; sparse/noncontiguous runs проходят прежний checked path. Для полностью contiguous range возвращаются непосредственно проверенные bytes, без промежуточных копий.
- Harness скрывает сравнение производительности при любом расхождении размера или SHA-256. В обоих финальных прогонах содержимое совпало, поэтому ratio относятся к одинаковой полезной нагрузке.
6. Как повторить сравнение на другой машине¶
pytsk3 на этой платформе установился готовым manylinux wheel'ом
(pip install pytsk3, версия 20260715), отдельная сборка не понадобилась.
Если на целевой машине wheel'а нет (нет компилятора/заголовков libtsk):
- Установите системные зависимости:
libtsk-dev(или эквивалент) и компилятор (build-essential/ VS Build Tools). pip install pytsk3— соберёт binding из исходников (займёт несколько минут; нужен совместимый Python ≤ 3.12 на момент написания).- Либо используйте prebuilt-окружение (например, образ с установленным
sleuthkit/pytsk3) и запустите
scripts/run_vfs_benchmarks.shв нём. - Если pytsk3 недоступен, запустите
--baseline native: harness запишетpytsk3_errorс точной причиной, а числовое сравнение публиковать нельзя — тогда приложите к результату выводpip install pytsk3и причину отказа.