|
nedoPC.orgCommunity for electronics hobbyists, established in 2002 |
|
Last visit was: 08 Nov 2024 15:35
|
It is currently 08 Nov 2024 15:35
|
Author |
Message |
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 23398 Location: Silicon Valley
|
Задумал я тут недоархиватор, чтобы декодер был поменьше чем MegaLZ, степень сжатия не хуже, ну и само-собой опенсорц Идея такая - сжатие на байтовом уровне (хаффмана могу попозже приделать) - байт #FF является ключевым (поэтому SHAFF ; ), после него идёт смещение до копии и длина повторяющегося блока. Если смещение/длина в диапазоне от 1 до 254, то ставим один байт, если больше - то #00 и далее два байта (т.е. всего будет три). Получается команда, начинающаяся с #FF, может занимать от 3 до 7 байт. Если встречается отдельно стоящий байт #FF, то он раздваивается - #FF #FF, чтобы декодер не принял его за начало команды. Некоторые последовательности можно объявить командами, например: #FF #00 #00 #00 #01 (смещение в виде #00 #00 #00 никогда не попадётся) может обозначать маркер конца блока. Повторяющиеся байты описываются последовательностью, которая ссылается сама на себя - например 16 нулей могут быть закодированы так: #00 #FF #01 #0F (сначала отдельный байт #00 далее команда копирования по смещению -1 блока длиной 15 байт). Архиватор работает с блоками 16К - чтобы охватить все возможные варианты повторений строим в памяти буфер 32Мб, в котором располагаем автокорреляционную матрицу с шагом от 1 до 16383. И далее "жадным" алгоритмом откусываем самые большие куски, пока не останутся только маленькие (3 и менее повторяющихся байт). Жмёт долго, но зато почти оптимально P.S. Обновление от 7 октября 2013:Заголовок файла SHAFF может выглядеть примерно так: 0...5 "SHAFF1" - первые 6 байт сигнатура и версия 6,7 - смещение от начала файла до данных первого блока в сетевом формате (big-endian) 8,9 - количество блоков (тоже в сетевом формате) 10,11 - длина последнего блока (она может быть меньше 16K) P.P.S. Обновление от 11 октября 2013:Например можно смещение после #FF представлять иначе: - если смещение равно нулю (#00), то это команда, заменяющая один символ #FF; - если старший бит 0, то смещение занимает 1 байт и может принимать значения от -1 до -127 (представляемые величинами от #01 до #7F); - если старшие два бита 1 и 0, то смещение занимает 1 байт и может принимать значения от -128 до -191 (представляемые величинами от #80 до #BF); - если старшие два бита 1 и 1, то смещение занимает 2 байта и может принимать значения от -192/#FF40 до -16383/#C001 (величины #C000 и #FF41...#FFFF не используются). Далее идёт размер блока - на нём также можно сэкономить, т.к. размеров больше 16383 (#3FFF) быть не может: - если старший бит 1, то размер занимает один байт и задаёт значения от 4 до 131 ( представляемые величинами от #80 до #FF или S+124 ); - если старшие два бита 0 и 1, то размер занимает 1 байт и задаёт значения от 132 до 195 ( представляемые величинами от #40 до #7F или S-68 ); - если старшие два бита 0 и 0, то размер занимает 2 байта и может принимать значения от 196/#00С4 до 16383/#3FFF (величины #0000...#00С3 не используются). В результате без особых усилий мы уменьшили максимальную длину FF-команды до 5 байтов (1+2+2). Последовательность вида #FF #С0 #00 может являться маркером конца блока, а в будущем ещё и #FF #FF [#41...#FF] можно будет задействовать для задания спец-команд... P.P.P.S. Обновление от 23 октября 2013 с небольшими уточнениями в январе 2017- окончательный формат для первой версии архиватора (в марте 2021 сделал описание более понятным): | | | | Code: SHAFF header:
Bytes 0...5 - signature and version ("SHAFF0", "SHAFF1" or "SHAFF2") Bytes 6,7 - offset to the 1st block of encoded data (big-endian) Bytes 8,9 - number of 16K blocks (big-endian) Bytes 10,11 - size of the last block (less or equal to 16384, also big-endian) Then optional uncompressed auxilary data, for example in case of SNA-file: Bytes 12...14 "SNA" - signature to identify SNA header Bytes 15...41 - SNA 27-byte header itself (for optional Huffman data in header after "HUF" signature see SHAFF2 description below) Then sequence of encoded 16KB blocks in chosen format:
SHAFF0 data format:
1st byte sets a Key to use further instead of #FF (but usually it's #FF) then any byte other than Key goes directly to output, but for Key see below #FF 00000000 - single byte #FF, otherwise #FF 0xxxxxxx LENGTH - distance 1..127 and LENGTH (see below) #FF 10xxxxxx LENGTH - distance 128..190 and LENGTH (see below), but #FF 10111111 LENGTH - reuses previous long distance -191 or longer #FF 11xxxxxx xxxxxxxx LENGTH - directly encoded long negative distance, but #FF 11000000 00000000 - end of block (with no length after) where LENGTH encoded by 1 or 2 bytes: 1xxxxxxx - for 4..131 01xxxxxx - for 132..195 00xxxxxx xxxxxxxx - direcly encoded length for up to 16383
SHAFF1 data format:
0xxxxxxx - single byte #00...#7F 10xxxxxxx - single byte #80...#FF 110000 - repeat last single byte (no LENGTH after that) 110001 LENGTH - repeat last distance longer than -1 and not equal to previous 110010 LENGTH - repeat previous last distance longer than -1 110011 LENGTH - distance -1 (that basically means copy of the last byte LENGTH times) 1101xxxxxx LENGTH - distance from -2 (000000) to -65 (111111) 11100xxxxxxxx LENGTH - distance from -66 (00000000) to -321 (11111111) 11101xxxxxxxxxx LENGTH - distance from -322 (0000000000) to -1345 (1111111111) 1111xxxxxxxxxxxxxx LENGTH - distance up to -16383 (directly encoded with prefix 11) special case without LENGTH: 111100000000000000 - end of block (after that last byte padded by zero bits) and anything above 111111101010111110 is reserved! LENGTH is a sequence of 2...26 bits that encode length of the copy: 0x - for 2 (encoded by 0) and 3 (encoded by 1) 10xx - for 4 (00), 5 (01), 6 (10), 7 (11) 110xxx - for 8...15 (000...111) 1110xxxx - for 16...31 (0000...1111) 11110xxxxx - for 32...63 (00000...11111) 111110xxxxxx - for 64...127 (000000...111111) 1111110xxxxxxx - for 128...255 (0000000...1111111) 11111110xxxxxxxx - for 256...511 (00000000...11111111) 111111110xxxxxxxxx - for 512...1023 (000000000...111111111) 1111111110xxxxxxxxxx - for 1024...2047 (0000000000...1111111111) 11111111110xxxxxxxxxxx - for 2048...4095 (00000000000...11111111111) 111111111110xxxxxxxxxxxx - for 4096...8191 (000000000000...111111111111) 1111111111110xxxxxxxxxxxxx - for 8192...16383 (0000000000000...1111111111111)
SHAFF2 data format:
00... \ 01... - Huffman codes for literals 10... / 11... - Reference that encoded the same way as in SHAFF1 above
Optional Huffman table after "HUF" signature in header: #FF N - start section with codes that have length N (1...254) XX ... - code for byte XX (Huffman code starts with most significant bit) #FF #00 ... - code for byte #FF #FF #FF - end of HUF table
If "HUF" signature is not found in the header then it means depacker must use internal Huffman table for English text in ASCII format. Other internal tables will also be supported as HUF1 for Russian text in alternative DOS codepage, HUF2 for Russian text in UTF-8 format with UNIX line ending and HUF3 for C/C++ source code also with UNIX line ending.
| | | | |
P.P.P.P.S. Исходники тут (к 8 февраля 2017 реализовал оба метода SHAFF0 и SHAFF1, а 12 февраля 2017 добавил экспериментальную поддержку SHAFF2, ну и в июне 2018 это всё переехало с GitHub на GitLab):
https://gitlab.com/shaos/shaff P.P.P.P.P.S. На самом деле SHAFF не LZ77-like, а LZSS-like паковщик (как и большинство архиваторов и пакеров, существующих в данный момент) т.к. мы работаем с одним потоком данных, в котором перемежаются литералы и ссылки, идентифицирующиеся по префиксам: https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Storer%E2%80%93Szymanski https://ru.wikipedia.org/wiki/LZSS (по русски) P.P.P.P.P.P.S. Пока выложил EXE-шник последней версии 1.2 от 2017 года под винды, кому интересно: https://gitlab.com/shaos/shaff/-/tree/master/binaries/win32
Last edited by Shaos on 25 Oct 2013 16:53, edited 15 times in total.
|
07 Oct 2013 01:24 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 23398 Location: Silicon Valley
|
Живой пример - файл chess.sna (49K), который обычный ZIP жмёт в 8.8K (MegaLZ должен жать примерно также). Разбиваем его на 3 блока по 16K и жмём с помощью SHAFF:
В итоге потратив 23 минуты на Pentium-4 получаем 10.5К - и это без Хаффмана Другой пример - файл cybotron.sna (49K), который обычный ZIP жмёт в 5К (игра для 16К спектрума - большая часть снапшота это нули):
За 19 минут на Pentium-4 получили 6К
P.S. Боюсь представить сколько это будет если сверху пройтись Хаффманом...
Last edited by Shaos on 11 Oct 2013 20:41, edited 3 times in total.
|
07 Oct 2013 01:33 |
|
|
vinxru
Retired
Joined: 27 Mar 2013 04:55 Posts: 587 Location: 62.192.229.16
|
Перед тем как мне дали в руки MegaLZ, я написал свой архиватор по алгоритму LZW ( http://ru.wikipedia.org/wiki/LZW)
Скорость сжатия и распаковки получилась очень быстрой, раза в два медленнее обычного копирования память-память. И сжатие не сильно хуже MegaLZ.
Но алгоритм требовал место в памяти под словарь. Вроде бы 512 байт, я сейчас не помню точно.
У MegaLZ есть гигантское преимущество - он не требует доп памяти и исходный архив читает строго последовательно.
|
07 Oct 2013 01:36 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 23398 Location: Silicon Valley
|
У меня тоже словаря нет - по сути это сильно упрощённый MegaLZ (а точнее что-то типа LZ77, но где "окно" никуда не "скользит", а растёт от начала кодируемого буфера), где убрана вся алхимия с битовыми последовательностями. Кстати я могу этот архиватор заставить опционально сохранять файл в формате MegaLZ...
|
07 Oct 2013 01:39 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 23398 Location: Silicon Valley
|
Ещё примеры: Файл tomahawk.sna (49K) - сжимается ZIP в 28.8K, а у нас: получилось 35.9К за 93 минуты. Файл exolon.sna (49K) - сжимается ZIP в 25.6К, а у нас: получилось 31.2К за 67 минут. Файл buzzsaw.sna (49K) - сжимается ZIP в 24.4К, а у нас: получилось 28.5К за 55 минут. т.е. в 32К не влез только Tomahawk (надо попробовать Хаффманом по нему пройтись)... P.S. зная вероятности появления символов можно прикинуть хаффмановскую таблицу в онлайне: http://planetcalc.com/2481/
|
07 Oct 2013 05:49 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 23398 Location: Silicon Valley
|
Заголовок файла SHAFF может выглядеть примерно так:
0...5 "SHAFF1" - первые 6 байт сигнатура и версия
6,7 - смещение от начала файла до данных первого блока в сетевом формате (big-endian)
8,9 - количество блоков (тоже в сетевом формате)
10,11 - длина последнего блока (она может быть меньше 16K)
12,13... - далее 2-байтовые контрольные суммы по одной на блок
перед непосредственно данными может идти дополнительная информация, например содержимое регистров для снапшота и/или таблица кодирования по Хаффману...
P.S. Вот так выглядит процесс сжатия снапшота пустого бейсика (в алгоритме есть косяк - разбираюсь):
P.S. Всё-таки надо постараться обойтись без Хаффмана - можно попробовать сильнее разнообразить способ представления аргументов команды #FF - не так разнообразно как в MegaLZ, но лучше чем только 1 vs 3 байта, как озвучено в первом посте этого топика - для этого можно проанализировать распределение размеров и смещений на нескольких реальных примерах, чтобы определить оптимальный битовый размер этих двух параметров...
|
07 Oct 2013 17:54 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 23398 Location: Silicon Valley
|
Косяк исправил - вот правильное сжатие памяти ZX-48K:
В конце видно таблицу вероятностей - по ней можно построить таблицу Хаффмана и прикинуть насколько оно ещё сожмётся: P.S. В таблице 0x100 это на самом деле команда #FF - просто я её отдельно от символа #FF учитываю, чтобы посмотреть имеет смысл её отдельно в Хаффмане считать или нет... P.P.S. С раздельными FF получилось 526 без Хаффмана и 334 с Хаффманом. Теперь пробуем объединённые коды #FF (15 и 41 будет 2*15+41=71 для #FF):
Подсчёт показывает те же 334 байта, т.е. выгоды в разделении команды #FF и символа #FF практически нет...
P.P.P.S. Кстати ZIP сжимает zx48.sna в 672 байта - выходит SHAFF даже без Хаффмана сжимает лучше
С другой стороны для Хаффмана ещё и таблицу надо хранить...
P.P.P.P.S. GZIP сжимает в 547, BZIP2 в 502, а XZ в 548...
|
10 Oct 2013 16:50 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 23398 Location: Silicon Valley
|
А теперь попробуем пойти на маленькую хитрость - разрешим 3-байтовые последовательности, которые без Хаффмана не имеют смысла:
Хаффмановский калькулятор на это выдал вот такую табличку:
Что даёт 341 байт - даже больше, где мы остановились на 4-байтовых комбинациях...
|
10 Oct 2013 19:04 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 23398 Location: Silicon Valley
|
Итак с исправленным архиватором имеем (без Хаффмана):
zx48.sna - 0.5K
chess.sna - 10.5K cybotron.sna - 6.1K Для примера захаффманим chess:
C Хаффиманом получается 8.9К, т.е. почти как ZIP
|
10 Oct 2013 19:39 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 23398 Location: Silicon Valley
|
Результаты по другим [недо]архиваторам: zx48.sna (49K): SHAFFv1h - 431 SHAFFv1g - 428 SHAFFv1f - 435 SHAFFv1e - 429 SHAFFv1d - 439 SHAFFv1c - 438 SHAFFv1b - 570 SHAFFv1a - 526 SHAFFv1a+Huffman - 333 MegzLZ - 1021 Hrum - 926 Hrust - 478 ZIP - 672 GZIP - 547 BZIP2 - 502 RAR 3.80 - 552 XZ (LZMA2) - 548 chess.sna (49K): SHAFFv1h - 8.6K SHAFFv1g - 8.6K SHAFFv1f - 8.6K SHAFFv1e - 8.7K SHAFFv1d - 8.8K SHAFFv1c - 8.7K SHAFFv1b - 10.3K SHAFFv1a - 10.5K SHAFFv1a+Huffman - 8.9K MegaLZ - 9.0K Hrum - 8.9K Hrust - 8.5K ZIP - 8.8K GZIP - 8.8K BZIP2 - 9.3K RAR 3.80 - 8.3K XZ (LZMA2) - 8.0K cybotron.sna (49K): SHAFFv1h - 4.8K SHAFFv1g - 4.8K SHAFFv1f - 4.8K SHAFFv1e - 4.8K SHAFFv1d - 4.9K SHAFFv1c - 4.8K SHAFFv1b - 6.0K SHAFFv1a - 6.1K MegaLZ - 5.2K Hrum - 5.2K Hrust - 4.7K ZIP - 5.0K GZIP - 4.9K BZIP2 - 5.2K RAR 3.80 - 4.7K XZ (LZMA2) - 4.4K И оставшиеся SNA-файлы: tomahawk.sna (49K): SHAFFv1h - 29.5K SHAFFv1g - 29.6K SHAFFv1f - 29.6K SHAFFv1e - 29.9K SHAFFv1d - 30.0K SHAFFv1c - 29.9K SHAFFv1b - 35.6K SHAFFv1a - 36.2K MegaLZ - 29.0K Hrum - 29.2K Hrust - 28.4K ZIP - 28.8K GZIP - 28.7K BZIP2 - 30.1K RAR 3.80 - 27.5K XZ (LZMA2) - 25.6K exolon.sna (49K): SHAFFv1h - 26.2K SHAFFv1g - 26.2K SHAFFv1f - 26.3K SHAFFv1e - 26.4K SHAFFv1d - 26.6K SHAFFv1c - 26.5K SHAFFv1b - 31.2K SHAFFv1a - 31.3K MegaLZ - 25.7K Hrum - 25.9K Hrust - 25.1K ZIP - 25.6K GZIP - 25.5K BZIP2 - 27.8K RAR 3.80 - 24.5K XZ (LZMA2) - 22.9K buzzsaw.sna (49K): SHAFFv1h - 25.1K SHAFFv1g - 25.0K SHAFFv1f - 25.0K SHAFFv1e - 25.1K SHAFFv1d - 25.4K SHAFFv1c - 25.2K SHAFFv1b - 28.5K SHAFFv1a - 28.4K MegaLZ - 24.3K Hrum - 24.3K Hrust - 23.1K ZIP - 24.4K GZIP - 24.2K BZIP2 - 25.1K RAR 3.80 - 15.1K XZ (LZMA2) - 17.1K bozxle.sna (49K):
SHAFFv1h - 20.2K
SHAFFv1g - 20.3K
SHAFFv1f - 20.2K
SHAFFv1e - 20.4K
SHAFFv1c - 20.4K
SHAFFv1b - 24.5K
MegaLZ - 20.1K
Hrum - 20.1K
Hrust - 19.5K
ZIP - 19.5K
GZIP - 19.3K
BZIP2 - 21.0K
RAR 3.80 - 18.9K
XZ (LZMA2) - 17.1K
Возникает вопрос - выкатывать архиватор в таком виде как версию 1 и думать над более компактным представлением в следующих версиях либо поресёрчить ещё?...
P.S. Например можно смещение после #FF представлять иначе:
- если смещение равно нулю (#00), то это команда, заменяющая один символ #FF;
- если старший бит 0, то смещение занимает 1 байт и может принимать значения от -1 до -127 (представляемые величинами от #01 до #7F);
- если старшие два бита 1 и 0, то смещение занимает 1 байт и может принимать значения от -128 до -191 (представляемые величинами от #80 до #BF);
- если старшие два бита 1 и 1, то смещение занимает 2 байта и может принимать значения от -192/#FF40 до -16383/#C001 (величины #C000 и #FF41...#FFFF не используются).
Далее идёт размер блока - на нём также можно сэкономить, т.к. размеров больше 16383 (#3FFF) быть не может:
- если старший бит 1, то размер занимает один байт и задаёт значения от 4 до 131 ( представляемые величинами от #80 до #FF или S+124 );
- если старшие два бита 0 и 1, то размер занимает 1 байт и задаёт значения от 132 до 195 ( представляемые величинами от #40 до #7F или S-68 );
- если старшие два бита 0 и 0, то размер занимает 2 байта и может принимать значения от 196/#00С4 до 16383/#3FFF (величины #0000...#00С3 не используются).
В результате без особых усилий мы уменьшили максимальную длину FF-команды до 5 байтов (1+2+2).
P.P.S. Последовательность вида #FF #С0 #00 может являться маркером конца блока, а в будущем ещё и #FF #FF [#41...#FF] можно будет задействовать для задания спец-команд...
P.P.P.S. Результаты подкорректированного алгоритма представления сжатых данных обозначены выше как SHAFFv1b (уже с учётом SNA-хедера и маркера конца блоков). Выгода по сравнению с SHAFFv1a есть, но не сильно существенная....
P.P.P.P.S. 15 октября добавил сюда результаты по битовопоточному SHAFF под названием SHAFFv1c - он даже MegaLZ бывает обгоняет, но вот Hrust похоже недосягаем...
P.P.P.P.P.S. 17 октября добавляю результаты SHAFFv1d - финальный прототип для версии 1. Хотя чего-то он худшие результаты показывает, чем SHAFFv1c - видимо надо подкорректировать формат ещё раз
P.P.P.P.P.P.S. 20 октября добавляю результаты SHAFFv1e - на этот раз точно финальный прототип для версии 1. Он обгоняет MegaLZ на данных, где есть много повторяющихся байтов, но отстаёт, если их мало. Но зато все имеющиеся SNA-файлы влезают в 32К...
P.P.P.P.P.P.P.S. 21 октября добавляю результаты SHAFFv1f - он чуть лучше, а затем и SHAFFv1g - он ещё лучше...
P.P.P.P.P.P.P.P.S. 24 октября добавил результаты по SHAFFv1h - он где-то лучше, а где-то хуже...
Last edited by Shaos on 24 Oct 2013 05:51, edited 33 times in total.
|
11 Oct 2013 05:09 |
|
|
alone
Writer
Joined: 06 Sep 2007 07:05 Posts: 19 Location: 212.26.238.228
|
А используется "оптимальный LZH" или хотя бы Lazy Evaluation? В MegaLZ "оптимальный LZH". От этого зависит сила сжатия.
|
12 Oct 2013 00:26 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 23398 Location: Silicon Valley
|
Это оптимальный LZ77-like, но не совсем оптимальное представление (без Хаффмана - с Хаффманом как раз всё хорошо)
|
12 Oct 2013 01:05 |
|
|
alone
Writer
Joined: 06 Sep 2007 07:05 Posts: 19 Location: 212.26.238.228
|
Оптимальный в том смысле, что из всех способов разбиения последовательности на литералы и ссылки выбирается самый оптимальный (идёт обсчёт всех путей, потом выбирается самый короткий).
|
12 Oct 2013 02:56 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 23398 Location: Silicon Valley
|
У меня всегда выкусывается самая большая из оставшихся повторяющихся последовательностей - в этом смысле алгоритм "оптимален", однако представление информации, когда команда имеет в виде префикса целый байт #FF, не совсем годится для кодирования коротких последовательностей. Сейчас рассматриваю варианты "постобработки" без скатывания в побитовую алхимию (Хаффман и т.д.) - например после LZ77 (со "скользящим" окном) можно применить табличный LZ на оставшиеся непожатые данные, ищущий и кодирующий часто повторяющиеся "триады" (тройки байтов) одним байтом (для подстановки выбираются не встречающиеся или редко встречающиеся байты). Сейчас у меня задача любой SNA упаковывать в 32К (пока из тех что я пробую не уталкивается в 32К только файл tomahawk.sna).
Last edited by Shaos on 13 Oct 2013 09:45, edited 3 times in total.
|
13 Oct 2013 08:29 |
|
|
alone
Writer
Joined: 06 Sep 2007 07:05 Posts: 19 Location: 212.26.238.228
|
Самая большая - это не оптимальный алгоритм. Оптимальный учитывает и способ кодирования с точностью до бита. Это реально реализовано в 7zip и MegaLZ.
|
13 Oct 2013 08:45 |
|
Who is online |
Users browsing this forum: Claude AI [Bot] and 0 guests |
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot post attachments in this forum
|
|