Як ми побудували компресор, що перемагає gzip-9: експеримент з autoresearch

Експеримент з autoresearch в автономній AI-оптимізації | English

AI-агент провів ~155 автономних експериментів за чотири сесії, еволюціонувавши наївну реалізацію LZ77 до компресора з rANS ентропійним кодером, контекстним моделюванням та оптимальним парсингом — перемігши gzip-9 на 28%, zstd-19 та bzip2, і наблизившись до brotli-11 на 0.9%.

Ідея

Андрій Карпатий випустив autoresearch у березні 2026 — цикл, де AI-агент редагує код, запускає бенчмарки, зберігає покращення, відкочує невдачі і повторює. Його демо оптимізувало тренування LLM на одному GPU. Концепція проста: автоматизувати науковий метод, поки ти спиш.

Я хотів перевірити, чи працює це за межами ML. Адаптував autoresearch як MCP-сервер для Claude Code і поставив виклик: почати з голого LZ77-компресора на Rust, дозволити агенту оптимізувати його автономно, подивитися, як далеко він зайде.

Правила прості: жодних зовнішніх залежностей, всі тести мають проходити, формат стиснення можна змінювати вільно, не шахраювати на бенчмарках.

Налаштування експерименту

Ми створили experiment/ зі свіжим git-репозиторієм, Rust-проектом і двома файлами:

Для тестових даних ми взяли 12 файлів з корпусу LibDeflate і додали два тексти української літератури Івана Франка — загалом 3.46 МБ: англійська проза, вихідний код, HTML, бінарні файли Excel, protobuf, JPEG, випадкові дані та кириличний текст.

Бенчмарк-скрипт (autoresearch.sh) компілював у release-режимі, стискав кожен файл 5 разів і виводив METRIC ratio=X.XXXXXX. Окремий autoresearch.checks.sh запускав cargo test --release після кожного успішного бенчмарку, щоб гарантувати коректність roundtrip на всіх 16 тестових файлах.

Фаза 1: Оптимізація швидкості чистого LZ77 (експерименти 1-22)

Стартова точка — підручниковий LZ77: хеш-ланцюговий пошук збігів, ковзне вікно 32 КБ, жадібний матчинг і найпростіший формат — 0x00 <байт> для літералів, 0x01 <довжина> <дистанція> для збігів.

Базова лінія: ratio 0.754, загальний час 19.8мс.

Первинна метрика — швидкість (загальна кількість мікросекунд на стиснення + розтиснення). Агент ітерував:

  1. Налаштування MAX_CHAIN. Найпотужніший важіль. Зменшення довжини хеш-ланцюга з 256 до 32 скоротило час стиснення на 60% — з 12.6мс до 4.9мс. Ланцюг був домінуючим вузьким місцем: кожна позиція перебирала до 256 кандидатів побайтово. При довжині 32 більшість хороших збігів все ще знаходилися. Зменшення до 16 було надто агресивним — ratio деградував настільки, що сповільнив декомпресію.
  2. Unsafe всюди. Заміна перевірених доступів до масивів на get_unchecked, використання ptr::read_unaligned для 8-байтного порівняння збігів та copy_nonoverlapping для копіювання в декомпресорі. Кожна зміна давала 1-5%, але вони накопичувалися.
  3. Кращий хеш. Перехід зі shift-XOR на 4-байтний мультиплікативний хеш (v.wrapping_mul(0x1E35A7BD) >> (32 - HASH_BITS)). Менше колізій означало коротші ефективні ланцюги, кращі збіги і прискорення на 6%.
  4. Детекція нестисних регіонів. Після 128 послідовних позицій без збігу — припинити вставку в хеш-таблицю. Стиснення JPEG прискорилося з 1264 до 350 мікросекунд.

Багато ідей провалилися: LTO (нульовий ефект на одному крейті), target-cpu=native (Apple Silicon і так добре оптимізований), Box<[u32]> замість Vec<u32> (гірше), prefetch-інтринсіки (нестабільні на stable Rust), XOR-shift хеш (жахливий розподіл — kennedy.xls подвоїв час).

Результат після 22 експериментів: ratio 0.754 (без змін), час 3.5мс (в 5.6 разів швидше).

Фаза 2: Компактний формат — прорив у ratio (експерименти 22-25)

Швидкість вийшла на плато. Агент помітив, що бінарні дані розширюються — кожен літерал коштував 2 байти в наївному форматі. Нестисний JPEG зростав зі 123 КБ до 246 КБ.

Рішення — нове кодування:

Ratio впав з 0.754 до 0.452 — на 40% менший вихід. JPEG ледь розширювався (1.007x). Зміна формату стала найбільшим покращенням ratio за весь експеримент.

Фаза 3: Кодування Гаффмана — стаємо DEFLATE (експерименти 27-35)

Компактний формат кодував літерали як сирі байти (8 біт кожен). Але в англійському тексті 'e' зустрічається набагато частіше за 'z'. Кодування Гаффмана призначає коротші коди частішим символам.

Перша спроба: окремий Гаффман для літералів + фіксована ширина для збігів. Ratio покращився, але швидкість деградувала через побітові теги токенів.

Прорив: об'єднаний алфавіт у стилі DEFLATE. Символи 0-255 = літерали, 256 = кінець блоку, 257-285 = довжини збігів. Одне дерево Гаффмана для всього — жодного побітового тегу. Дистанції отримали власне дерево. По суті це те, що робить gzip.

Реалізація Гаффмана виявилася нетривіальною. Перша версія мала нескінченний цикл у виправленні нерівності Крафтаbl_count[MAX_BITS] -= 1 міг стати від'ємним (underflow до u32::MAX), коли лічильник вже був нулем, і цикл розходився. Виправлено простою перевіркою.

Ratio: з 0.452 до 0.337. Ще на 25% менше.

MAX_CHAIN було перенастроєно: з Гаффманом коротші ланцюги стали прийнятними, бо ентропійний кодер компенсував гірші збіги. Зменшили з 32 до 8 — час стиснення повернувся з 28мс до 24мс.

Фаза 4: Оптимальний парсинг — революція динамічного програмування (експерименти 36-47)

Жадібний матчинг (завжди бери найдовший збіг) — швидкий, але субоптимальний. Уявіть: 4-байтний збіг на позиції i може запобігти 20-байтному збігу на позиції i+1. Краще видати один літерал і взяти довший збіг.

Агент реалізував пряме динамічне програмування: на кожній позиції оцінити вартість (у бітах Гаффмана) видачі літерала проти кожної можливої довжини збігу. Пройти вперед по вхідних даних, підтримуючи мінімально-вартісні прибуття. В кінці — бектрек для отримання оптимальної послідовності токенів.

Два ключових вдосконалення:

  1. Ітеративне ціноутворення. Перший прохід DP використовує статичні ціни (літерал = 8 біт, збіг = 11+додаткові). Будуємо Гаффмана з результату, потім перезапускаємо DP з реальними довжинами кодів Гаффмана як цінами. Два проходи сходяться; третій додає лише 0.02%.
  2. Мульти-довжинні кандидати. Не лише найдовший збіг і MIN_MATCH — пробуємо довжини на кожній межі довжинного коду DEFLATE (3, 4, 6, 10, 19, 35, 67, max). DP знаходить оптимальний поділ.

Ratio: з 0.337 до 0.306. Ми перетнули лінію gzip-9 (0.303 на оригінальному 10-файловому корпусі).

DP був повільним (100мс проти 19мс жадібного) через виділення масивів вартості + бектреку на весь вхід. Об'єднання пошуку збігів з DP в одному проході усунуло тимчасові масиви. MAX_CHAIN повернувся до 32 — з DP кращі збіги від довших ланцюгів виправдовували витрати.

Фаза 5: Контекстне моделювання та налаштування (експерименти 48-57)

Вивчення Brotli та PAQ відкрило наступний рубіж: контекстне моделювання. Після пробілу наступний байт майже напевно — маленька літера. Після 'q' — майже напевно 'u'. Одна таблиця Гаффмана не може це вловити.

Ми реалізували контекстне моделювання першого порядку з 4 класами:

Чотири окремі таблиці Гаффмана, що обираються на основі попереднього декодованого байта. DP був оновлений для використання контекстно-залежних цін — lit_price(byte, context_class(input[i-1]), ll_bits) замість lit_price(byte, ll_bits).

Критичне відкриття: адаптивний розмір заголовка. Чотири таблиці Гаффмана додають 858 байтів заголовка (4 x 286 довжин кодів). На маленьких файлах цей overhead знищує виграш від кодування. Рішення: використовувати 4 таблиці лише коли кількість токенів перевищує 32 000. Менші файли повертаються до однієї таблиці з 330-байтним заголовком. Одне налаштування цього порогу покращило середній ratio на 0.5%.

MAX_CHAIN був збільшений до 128 — з контекстно-залежним DP кожен додатковий збіг, знайдений довшими ланцюгами, використовувався ефективніше.

Фінальний ratio: 0.288.

Фінальне порівняння (після фази 5)

Файл                  Наш     gzip-9   Зміна
alice29.txt          0.350    0.360    -2.7%
kennedy.xls          0.174    0.201   -13.6%
plrabn12.txt         0.394    0.410    -4.0%
urls.10K             0.299    0.312    -4.2%
franko-berkut.html   0.264    0.275    -4.1%
franko-lys.rtf       0.085    0.082    +3.5%
СЕРЕДНЄ              0.288    0.303    -5.0%

Ми перемагаємо gzip-9 на 8 з 10 стисних файлів. Два програші — маленький файл (fields.c — overhead заголовка) та RTF-файл, де більше ефективне вікно gzip допомогло.

Швидкість: 548мс стиснення (проти 464мс gzip-9), 9мс розтиснення (проти ~40мс gzip-9). Наш декомпресор в 4 рази швидший завдяки 12-бітній lookup-таблиці Гаффмана, що декодує символи за один доступ.

Проти сильніших компресорів: bzip2 (0.229), zstd-19 (0.226), brotli-11 (0.217) і 7z (0.217) — всі перемагають нас. Вони використовують техніки, які ми не реалізували: BWT-трансформацію, FSE ентропійне кодування, rep-offsets, контекстне мікшування та вікна до 16 МБ. Наше вікно 32 КБ — найбільше обмеження, що залишилось — kennedy.xls стискається до 0.174 у нас, але до 0.051 у 7z.

Фаза 6: Rep-offsets та нестандартні підходи (експерименти 58-75)

Вивчивши архітектуру zstd, LZMA та RAR, ми реалізували rep-offsets — техніку збереження останніх використаних дистанцій. Коли поточний матч має таку саму дистанцію як попередній, замість повного кодування дистанції (5-15 біт) використовується спеціальний символ REP0 (~2 біти).

GPT 5.4 запропонував три конкретних ідеї: rep-offsets, distance context by match length, та adaptive block splitting. Ми почали з rep-offsets:

  1. REP0/REP1 post-hoc — при кодуванні перевіряємо чи дистанція збігається з однією з двох останніх. Якщо так — кодуємо спеціальним символом. kennedy.xls отримав -8.9% одразу.
  2. Rep-distance в DP — критичне покращення. Додали dp_rep масив що трекає останню використану дистанцію вздовж оптимального шляху. Тепер DP може свідомо обирати коротший матч з rep-distance замість довшого з новою дистанцією, якщо перший кодується дешевше. kennedy.xls: -10.4% додатково.
  3. 8 контекстних класів — розділили lowercase на голосні/приголосні, додали окремий клас для non-ASCII. Це допомогло на текстах з мішаним контентом.
  4. Зворотна компресія — нестандартна ідея від Gemini 3.1: спробувати стиснути файл задом наперед і зберегти менший результат. HTML-файли стискаються краще у зворотному напрямку через закриваючі теги, які дзеркалять відкриваючі.
  5. RLE-кодування заголовка Huffman — наш заголовок мав 2288 байт (8 таблиць × 286 code lengths). Більшість значень — нулі або повторення. Просте RLE-кодування зменшило заголовок до ~1100 байт, що покращило ratio кожного файлу.
  6. CRC32 верифікація — додали чексуму для перевірки цілісності: CRC32 оригінальних даних зберігається в заголовку стисненого файлу.

Фаза 7: Збільшення вікна — другий великий прорив (експерименти 76-80)

Найбільший залишковий ліміт — розмір sliding window. 32 КБ (як у DEFLATE) означає що повторення на відстанях > 32768 байт не знаходяться. urls.10K має URL-патерни що повторюються через 50-100 КБ.

Збільшення вікна потребувало серйозних змін формату:

Результат виявився вражаючим:

Це був найбільший одиничний стрибок за весь сегмент — більше ніж усі інші оптимізації разом.

Фаза 8: Глибока оптимізація DP та REP-трекінг (експерименти 81-140)

Третя сесія autoresearch сфокусувалась на вичавлюванні максимуму з існуючої архітектури через 60 додаткових експериментів. Головний підхід — дати DP більше інформації і більше варіантів вибору.

REP1 трекінг в DP — найбільший прорив сесії

Раніше DP трекав лише одну останню дистанцію (dp_rep). Encoder же підтримує REP0-REP3 (4 збережених дистанції). Додавання dp_rep1 — другої останньої дистанції в DP — дало найбільший одиничний стрибок: kennedy.xls впав з 0.130 до 0.074 (-43%!). DP тепер може свідомо обирати матчі що створюють вигідні патерни чергування дистанцій.

REP2/REP3 + REP в DP

Dense try_lens — друга революція

DP раніше пробував лише 7 довжин: [3, 6, 19, 67, 258, 515, max]. Gemini 3.1 Pro при ревю коду підказав: "тестуйте ВСІ довжини до 16". Результат вражаючий:

DP тепер точно обирає де "обрізати" матч щоб наступний матч почався вигідніше. Без dense try_lens DP бачив лише [3, 6, 19...] і мусив стрибати через велику діру між 6 і 19.

Fractional-bit DP pricing

Замінили цілочисельні ціни Huffman (u8 біт) на дробові ціни (u16 у форматі 8.8 fixed-point). Ціни ініціалізуються з Shannon entropy (-log2(freq/total) * 256), а в останніх проходах DP — з реальних довжин кодів Huffman. Цей гібридний підхід (entropy для exploration, Huffman для precision) дає кращу конвергенцію ніж чистий entropy або чистий Huffman.

Адаптивний 5-байтний хеш

4-байтний хеш має багато колізій з великим вікном. 5-байтний хеш різко зменшує колізії, знаходячи кращі матчі на великих файлах. Але на малих файлах (< 32 КБ) 5-байтний хеш гірший — менше корисних колізій. Рішення: адаптивний хеш — 5-байтний для великих файлів, 4-байтний для малих.

Nibble-packed RLE заголовок

Довжини кодів Huffman — це числа 0-12, що поміщаються в 4 біти. Замість 1 байт/значення пакуємо 2 значення в 1 байт. З run-length кодуванням нулів і повторень заголовок зменшився приблизно вдвічі. fields.c покращився на 2.5%, geo на 1.0%.

Адаптивний вибір контексту (entropy-based)

Замість фіксованого порогу "8 таблиць якщо > 15000 токенів" — обчислюємо ентропію з 1 таблицею і з 8, порівнюємо з оцінкою overhead заголовка. Файли вибирають оптимальну кількість таблиць самостійно. fireworks.jpeg тепер стискається без context overhead (ratio 0.9999 замість 1.0076).

Що НЕ спрацювало

За 60 експериментів багато ідей було відкинуто:

Фаза 9: rANS ентропійний кодер та 8 МБ вікно (експерименти 141-155)

Вікно 8 МБ

Збільшення sliding window з 1 МБ до 8 МБ потребувало розширення distance code таблиці з 40 до 46 кодів та зсуву REP символів. Ключовий баг: поле dist_extra в структурі Tok було u16 (максимум 65535), а для дистанцій >262144 потрібно 17+ extra bits (максимум 2097151). Заміна на u32 виправила roundtrip failures.

Результат на enwik8 (100 МБ): ratio 0.2917→0.2700 (-7.4%). Майже зрівнялись з zstd-19 (0.2694). На 12-file corpus ефект мінімальний — жодний файл не перевищує 1 МБ.

rANS — дробова точність без зміни заголовка (для великих файлів)

Головний прорив сесії. Реалізували rANS (range Asymmetric Numeral Systems) — ентропійний кодер з дробовою точністю бітів. Ключова ідея: try-both підхід — компресор стискає файл і Huffman, і rANS, зберігає менший результат. Flag bit у заголовку вказує декодеру який кодер використаний.

Чому try-both:

rANS freq table зберігається в sparse форматі: count(u16) + [index(u16) + freq(u16)]... — тільки non-zero frequencies. Це ~3x компактніше ніж dense формат.

Перша спроба (dense freq table, 4.7 КБ) провалилась катастрофічно — alice29 +9.5%, cp.html +62%. Try-both з sparse header вирішив проблему повністю.

Фаза 10: Suffix Array та оптимізація продуктивності (експерименти 156-175)

Suffix Array match finder

Hash chain з MAX_CHAIN=8192 давав найкращі матчі, але O(n × 8192) на 100 МБ — це 40 хвилин. Suffix array гарантує оптимальні матчі за O(n × scan_range) де scan_range=32-64 — набагато менше.

Реалізували O(n log²n) suffix array (prefix doubling algorithm) з гібридним підходом: SA для файлів < 1 МБ, hash chain для більших. SA знаходить найдовший матч, hash chain (len=16) — найближчий за дистанцією. Два match slots подаються в DP.

Результат: ratio -0.8% від hash chain, але 3-7x швидша компресія. На enwik8: 5.8 хв замість 40 хв.

Block-based DP

Gemini 3.1 при ревю коду виявив критичну проблему: DP масиви для 100 МБ файлу займають ~4 ГБ RAM і повністю вибивають L3 кеш. Рішення: розбити DP на блоки 256 КБ що влазять в L2 кеш.

Реалізація: match arrays залишаються full-file (матчі посилаються на повне вікно 8 МБ), але cost[], prev_info[], dp_rep[] — лише 256 КБ. Між блоками передається rep-state і prev_byte. Backtrack всередині кожного блоку окремо.

Результат на enwik8: 126с → 76с (-40%) при ідентичному ratio. На малих файлах (<256 КБ) ефект мінімальний.

Конвеєризація обрахунків

Серія оптимізацій для великих файлів (>1 МБ):

Сумарний результат: enwik8 40 хв → 76с (1.3 хв), тобто 32x швидше.

Що НЕ спрацювало

Фінальне порівняння

Файл                  Наш     gzip-9   brotli-11  Зміна vs gzip
alice29.txt          0.329    0.360    0.310       -8.6%
asyoulik.txt         0.358    0.390    0.341       -8.2%
fields.c             0.255    0.260    0.227       -2.1%
cp.html              0.307    0.324    0.280       -5.3%
kennedy.xls          0.047    0.201    0.060      -76.6%  ← б'ємо brotli-11!
plrabn12.txt         0.359    0.410    0.345      -12.4%
urls.10K             0.218    0.312    0.210      -30.1%
geo.protodata        0.107    0.127    0.099      -15.5%
franko-lys.rtf       0.075    0.082    0.072       -8.9%
franko-berkut.html   0.235    0.275    0.221      -14.7%
СЕРЕДНЄ              0.221    0.303    0.217      -27.1%

Ми перемагаємо gzip-9 на всіх 10 стисних файлах, -27% загалом. kennedy.xls 0.04721% краще за brotli-11!

На enwik8 (100 МБ): ratio 0.285 (з оптимізованими параметрами), компресія 76 секунд. Б'ємо gzip-9 на 22%, brotli-6 на 8%.

Швидкість (in-process, fair benchmark)

12-file corpus (3.47 МБ):
  OURS:     248 MB/s decompress, ratio 0.221
  gzip-9:   486 MB/s decompress, ratio 0.304
  brotli-6: 532 MB/s decompress, ratio 0.242
  zstd-1:  1270 MB/s decompress, ratio 0.292

enwik8 (100 МБ):
  OURS:     1.3 MB/s compress, 111 MB/s decompress, ratio 0.285
  gzip-9:  36 MB/s compress, 306 MB/s decompress, ratio 0.365
  brotli-6: 35 MB/s compress, 365 MB/s decompress, ratio 0.310
  zstd-19:  2.1 MB/s compress, 794 MB/s decompress, ratio 0.269

Повна еволюція

ЕтапRatioТехніка
Базова лінія0.754Жадібний LZ77, хеш-ланцюги
+ оптимізація швидкості0.754Unsafe-доступ, кращий хеш, налаштування ланцюгів
+ компактний формат0.452Послідовності літералів, кодування збігів змінної довжини
+ Гаффман0.337Об'єднаний алфавіт літералів/довжин/дистанцій у стилі DEFLATE
+ оптимальний DP0.306Пряме DP з ітеративним ціноутворенням Гаффмана
+ контекстне моделювання0.288Гаффман першого порядку (8 таблиць), адаптивний поріг
+ rep-offsets0.274REP0/REP1 дистанції, rep-distance в DP
+ нестандартні підходи0.271Зворотна компресія, RLE заголовок, 8-байтний хеш
+ збільшення вікна0.253Динамічне вікно до 256 КБ
+ REP1 трекінг в DP0.231DP трекає 2 останніх дистанції, kennedy -43%
+ dense try_lens0.228DP пробує всі довжини 3-64, точніший парсинг
+ nibble RLE + адаптація0.227Компактніший заголовок, entropy-based вибір контексту
+ 8 МБ вікно0.22546 distance codes, enwik8 -7.4%
+ rANS (try-both)0.219Дробові біти для великих файлів, kennedy -28%
+ suffix array0.220Гарантовано оптимальні матчі, 7x швидше
+ block DP + speed0.221256 КБ блоки, enwik8 40 хв → 76с

Загалом: більш ніж в 3.4 рази краща компресія від чистого аркуша до компресора що перевершує gzip-9 на 27%, б'є bzip2 і brotli-6, і знаходиться в 1.8% від brotli-11 — побудованого за ~175 автономних експериментів з 32x оптимізацією швидкості.

Що ми дізналися про autoresearch

Цикл працює. Цикл гіпотеза → реалізація → вимірювання → збереження/відкат — дійсно продуктивний. Більшість експериментів провалюються (35 з 57 були відкинуті), але успіхи накопичуються.

Скиди контексту — справжній виклик. Контекстне вікно Claude Code заповнюється після ~15-20 експериментів. Команда /autoresearch-resume читає autoresearch.md та git log для відновлення контексту, але нюансовані інсайти (наприклад, "виправлення нерівності Крафта має баг з underflow u32") втрачаються. Бэклог autoresearch.ideas.md допомагає — хороші ідеї не зникають при скиді контексту.

Мульти-модельний підхід дав найкращі ідеї. Найбільші стрибки прийшли від алгоритмічних змін, а не від мікрооптимізацій. Ключовий інструмент — інтеграція кількох AI-моделей в цикл через MCP-сервери. Окрім основного агента Claude Opus 4, ми підключили GPT 5.4 (через @trishchuk/codex-mcp-tool) та Gemini 3.1 Pro (через @trishchuk/gemini-mcp-server) як MCP-сервери. Коли основний агент вичерпував ідеї, він делегував "мозковий штурм" іншим моделям:

Конфігурація в .mcp.json:

{
  "mcpServers": {
    "gemini-cli": {
      "command": "npx",
      "args": [
        "-y",
        "@trishchuk/gemini-mcp-server"
      ]
    },
    "codex-cli": {
      "command": "npx",
      "args": [
        "-y",
        "@trishchuk/codex-mcp-tool"
      ]
    }
  }
}

Це перетворило autoresearch з "одна модель крутить цикл" на колаборацію трьох AI-моделей, де кожна приносить свої сильні сторони.

Баги можуть бути катастрофічними. Нескінченний цикл нерівності Крафта в Гаффмані та OOM від контекстного моделювання — обидва "вбили" систему. autoresearch.checks.sh з cargo test ловив баги коректності, але ресурсні баги (нескінченні цикли, вичерпання пам'яті) потребували ручного втручання. Обмеження паралелізму тестів (RUST_TEST_THREADS=1) виявилося критичним.

Правило 80/20 працює агресивно. Вісім змін дають 95% покращення ratio: компактний формат (-40%), кодування Гаффмана (-25%), оптимальний DP (-7%), збільшення вікна (-5%), REP1 трекінг в DP (-7%), dense try_lens (-1.4%), rep-offsets (-3%), контекстне моделювання (-2%). Решта 100+ експериментів були інкрементальними або тупиками.

Розмір вікна важливіший за складність алгоритму. Збільшення sliding window з 32 КБ до 256 КБ дало більше покращення ratio ніж усі інші оптимізації останніх 30 експериментів разом. Це урок: перед тим як ускладнювати алгоритм, перевір чи прості параметри не дадуть більше.

Нестандартні ідеї працюють. Зворотна компресія (спробувати файл задом наперед), RLE-кодування Huffman заголовків, XOR-передбачення літералів — більшість не спрацювала, але деякі дали реальний виграш. Зворотна компресія покращила HTML-файли, а RLE заголовків допоміг кожному файлу без винятку.

Динамічна адаптація важливіша за фіксовані параметри. Адаптивний розмір вікна (від 1 КБ для маленьких файлів до 256 КБ для великих), адаптивна кількість контекстних таблиць (1 або 8 на основі ентропії), адаптивний хеш (4 або 5 байт), спроба reverse — всі ці рішення приймаються per-file на основі характеристик вхідних даних.

DP pricing — це мистецтво, не наука. Гібридний підхід (Shannon entropy для ранніх проходів, Huffman lengths для фінального) працює краще ніж чистий варіант будь-якого з них. "Теоретично коректний" фікс DP↔encoder rep-state sync катастрофічно погіршив ratio, бо дестабілізував конвергенцію. Практика перемагає теорію.

Dense try_lens — несподіваний виграш. Пробування всіх match lengths 3-32 замість sparse [3,6,19,67,...] дало -1.4% ratio. Ця ідея прийшла з code review від Gemini 3.1 Pro — свіжий погляд іншої моделі знайшов "очевидну" оптимізацію яку основний агент не помічав 50+ експериментів.

Спробуйте самі

# Клонувати та зібрати
git clone <repo> && cd experiment
cargo build --release

# Запустити бенчмарк
./autoresearch.sh

# Порівняти з gzip
./benchmark.sh

# Запустити власний цикл autoresearch
/autoresearch optimize <ваша ціль>

Компресор — ~900 рядків Rust без залежностей (lib.rs + huffman.rs + codes.rs + context.rs + checksum.rs + rle.rs + range_coder.rs). Вся історія експерименту — в autoresearch.jsonl — 140+ записів, що документують кожну гіпотезу, результат і рішення.