Как я писал LZ4 плагин компрессии для Reiser4

в 16:12, , рубрики: linux, lz4, reiser4, метки: , ,

image

Объяснять что такое Reiser4 и с чем его едят я не буду, т. к. на этот счет достаточно информации [1, 2] и повторять её я не вижу смысла. Поэтому начну пожалуй с того, что Reiser4 я решил опробовать в 2010 году, но из-за проблем с использования прозрачной компрессии совместно с упаковкой хвостов (как оказалось были проблемы в flush процедуре, которые на данный момент решены[3]) перешел обратно на ReiserFS. В 2013 году я узнал о том, что эта проблема решена [4] и я снова вернулся на Reiser4 (LZO1 на стационарной системе, на ноутбуке без сжатия). Через какое-то время я вспомнил про новости о «Чрезвычайно быстром алгоритме сжатия» LZ4, а так-же о том, что комьюнити Illumos добавило поддержку оного в ZFS. Тут меня посетила мысль: «А было-бы здорово будь в Reiser4 поддержка LZ4»! Вот я и начал «приделывать» его к Reiser4.

Сначала я просмотрел код плагина ccreg40 (как известно Reiser4 имеет плагиновую структуру). Все начинается с файла fs/reiser4/plugin/compress/compress.h в котором есть перечисление reiser4_compression_id:

typedef enum {
	LZO1_COMPRESSION_ID,
	GZIP1_COMPRESSION_ID,
	LAST_COMPRESSION_ID,
} reiser4_compression_id;

В нем обозначаются идентификационные номера того или иного алгоритма сжатия (по умолчанию доступны LZO1 и GZIP1). Последним в списке идет LAST_COMPRESSION_ID, который нужен для определения размеров различных таблиц содержащих информацию о алгоритмах и сопутствующих им функций.

Продолжаем мы в файле fs/reiser4/plugin/compress/compress.c, в котором мы уже непосредственно описываем функции. Всего 7-мь основных функций:

  • init() – Нужна, если алгоритм требует предварительной инициализации чего-либо. Ни GZIP1, ни LZO1, ни LZ4 этого не требуют, поэтому они просто возвращают 0.
  • overrun() – Возвращает максимальный размер «хвоста», который может образоваться при сжатии. К примеру если не учитывать «хвост», то при несжимаемых входящих данных, произойдет выход за пределы выходного буфера. К примеру для GZIP1 это значение составляет 0, для LZO1 “src_len/64+19”, а для LZ4 “src_len/255+16”.
  • alloc() – Выделяет память для нужд алгоритма.
  • free() – Освобождает память, выделенную для нужд алгоритма.
  • min_size_deflate () – Возвращает минимальный размер блока, который все еще имеет смысл сжимать.
  • compress() – Сжимает данные.
  • decompress() – Распаковывает данные.

Подробнее остановлюсь на функциях alloc()/free(). Один из аргументов которые они принимают, значится аргумент act типа tfm_action. tfm_action является перечислением описанным в заголовочном файле fs/reiser4/plugin/compress/compress.h (имеет такую-же структуру как и reiser4_compression_id), в котором два элемента TFMA_READ и TFMA_WRITE.

typedef enum {
	TFMA_READ,   /* decrypt, decompress */
	TFMA_WRITE,  /* encrypt, compress */
	TFMA_LAST
} tfm_action;

Таким образом можно определить момент, вызывания функции, при чтении или при записи. Некоторые алгоритмы требуют дополнительную память для декомпрессии, и таким образом мы правильно выделим нужное количество памяти. К примеру алгоритм GZIP1 требует дополнительной памяти и мы выделяем для него оную, а алгоритмы LZO1/LZ4 не требуют и мы не выделяем её.

Заканчивается все в том-же файле compress.c, описанием массива compression_plugins, в котором мы указываем тип плагина, его идентификационный номер, заголовок, функции и т. д.

	[LZ4_COMPRESSION_ID] = {
		.h = {
			.type_id = REISER4_COMPRESSION_PLUGIN_TYPE,
			.id = LZ4_COMPRESSION_ID,
			.pops = &compression_plugin_ops,
			.label = "lz4",
			.desc = "lz4 compression transform",
			.linkage = {NULL, NULL}
		},
		.init = lz4_init,
		.overrun = lz4_overrun,
		.alloc = lz4_alloc,
		.free = lz4_free,
		.min_size_deflate = lz4_min_size_deflate,
		.checksum = reiser4_adler32,
		.compress = lz4_compress,
		.decompress = lz4_decompress
 	}

Теперь о том, что я изменил в коде LZ4. Для начала я убрал весь код связанный с Microsoft Visual Studio (может когда-нибудь и соберут ядро Linux посредством компилятора MS VS, но явно это будет не в ближайшем будущем) и C++ (один extern “C”). Затем убрал код, связанный с оптимизацией для BigEndian систем, которая делала выходную информацию несовместимой с LittleEndian системами и код, позволяющий использовать стековую память вместо обычной (получится быстрее, но мы в ядре, нам такие вольности не пройдут даром). Напоследок убрал из кода функции malloc()/free(), добавив в список аргументов функций указатель на участок памяти, выделенной для нужд LZ4 (вспомните alloc()).

Ну а теперь самое главное, как все это работало… откровенно говоря, плохо. Плагин LZ4 работал медленнее и сжимал хуже плагина LZO1. Замеры проводились на живой системе, в однопользовательском режиме. В замер входила операция размонтирования раздела (чтоб сработали sync/flush процедуры и файлы полностью записались на диск). Производилось три теста: линейное запись/чтение на диск файла забитого нулями (из /dev/zero), линейное чтение/запись несжимаемого файла (предварительно взятого с /dev/urandom и записанного в память на tmpfs) и распаковка/сжатие исходных кодов ядра Linux версии 3.9.5. Из всех тестов, плагин с LZ4 показал преимущество только при записи/чтении файла с нулями. Во всех остальных тестах, LZO1 обошел LZ4 и по скорости сжатия/декомпрессии, и по конечному объему файлов.

В ходе дальнейших исследований (fullbench из состава LZ4 и lz4c vs lzop), было выяснено, что LZ4 теряет все свои свойства при блоках маленького размера, а проявляет заявленные свойства [5] только на больших блоках, к примеру в fullbench по умолчанию 4MiB, в lz4c 8MiB. Как выразился Эдуард Шишкин: «4MiB — это как-то многовато. LZO1 сжимает куски и много мельче..» [6]

Таким образом я выяснил, что для Reiser4 LZO1 является более предпочтительным вариантом, ежели LZ4. Кстати говоря, что-то мне подсказывает, что поддержка LZ4, которая была добавлена сообществом, в ZFS будет проявлять себя далеко не всегда (хотя по сравнению с LZJB всегда), и безуспешные попытки протолкнуть LZ4 в Linux [7] (в качестве возможности использования для сжатия ядра или initram) тому подтверждение. Что до LZ4 в btrfs… Эдуард Шишкин наглядно рассказал о том, что из себя представляет btrfs [8] и как ведется его разработка.


Патч Reiser4 для Linux 3.9
Патч LZ4 для Reiser4
P.S. Функцию LZ4_decompress_safe() немного переделать нужно, но смысла нет, поэтому я не стал.
[1] habrahabr.ru/post/45873/
[2] http://theoks.net/~onekopaka/Reiser4Site/v4.html
[3] marc.info/?l=reiserfs-devel&m=135146138331012&w=2
[4] sourceforge.net/p/reiser4/discussion/general/thread/2bca4f8e/
[5] code.google.com/p/lz4/
[6] sourceforge.net/p/reiser4/discussion/general/thread/780facb4/
[7] lwn.net/Articles/534168/
[8] habrahabr.ru/post/108629/

Автор: BratSinot

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js