MySQL клиент формата A4

в 9:09, , рубрики: c++, IOCCC, mysql, метки: , ,

Можно ли уместить исходники MySQL клиента на 1 страничке формата A4? Оказывается, если 8 кеглем (в принципе читаемо) и после обфускации, то можно! А если читаемо, без обфускации и 10 кеглем, то нельзя: надо целых 6 страничек.

В ходе работы над всяким у меня тут случайно получился крохотный, но работающий MySQL клиент размером чуть больще 1000 строк. Стало интересно, насколько компактнее можно сделать. Потратил половину воскресенья, изолировал и минимизировал код. В результате появился nanomysql, CLI клиент для MySQL, полные исходники которого занимают чуть менее 380 строк, менее 1800 токенов и примерно 10500 байт, и при этом компилируются и работают под Linux, Window, MacOS. Написано на C++ с абсолютным минимумом STL.

Скриншоты, циферки и прочие подробности уикэнд-сумасшествия по изготовлению наноклиента под катом.

Для начала обещанный мега-скриншот!

Z:worknanomysql>nanomysql.exe -u testme -p moo
connected to mysql 5.5.25a-log

nanomysql> use test
---
ok, 0 row(s)

nanomysql> select * from t
id, gid, body
---
2, 1, three abcx four
3, 1, five six seven abcx
4, 1, eight in abcx-nine
5, 1, one two three in abcm
6, 1, binlog ftw
100, 1, one two abcx
---
ok, 6 row(s)

nanomysql> quit
bye

Клиент умеет обрабатывать (отсылать либо принимать и разбирать) 7 видов сетевых пакетов: handshake, auth, query, ok, error, field, row. Чтобы прицепиться к серверу, отсылать запросы и показывать результаты, больше в общем-то и не надо. Так что начальная версия, обособленная в 1 файл и собранная веерными cut-n-paste из разных мест исходников (см. «грабим по площадям, только бы собраться») сразу была небольшая и занимала ~1250 строк. Поэтому цель развлечения была поставлена в 500 строк.

Эта версия умела заходить на сервер с жестко прошитым логином и паролем и делать один жесткой прошитый же тестовый запрос. Тупо выкинув вообще ненужный код (в основном выкидывались всякие совсем неиспользуемые методы прямо целиком), ну и дописав заодно маленький консольный query loop, ее довольно быстро удалось довести до ~700 строк.

На этой отметке пришлось лезть уже внутрь классов и методов. Тут источников экономии места случилось три штуки:

  1. сливание вместе классов (а изначально вместо класса MysqlDriver_t их было три штуки, NetInputBuffer_c + NetOutputBuffer_c + собственно MysqlDriver_t),
  2. занос методов внутрь декларации класса (экономим на копии сигнатуры и пустых строках),
  3. упрощение обобщенного кода внутри методов под наш конкретный случай.

Ну и понятно, всегда есть точечные зачистки отдельных строк. Этими нехитрыми мерами дошел до примерно ~500 строк.

Заодно в ходе спуска до отметки 500 добавил разбор опций командной строки и сделал вывод чуть покрасивее. Изменения перемешивались с другими, по ликвидации кода, и в целом коммиты, добавляющий функционал, уменьшали число строк. Делать больше функционала и при этом стирать код всегда очень приятно! Еще в ходе этой части стало ясно, что в погоне за строчками привычный кодстандарт придется слегка нарушать: полностью выкидывать assert()-ы, местами форматировать код «в столбик» вместо «елочки», а местами наоборот, «в строчку» вместо «столбика» и в целом писать несколько вразнобой. Ключевым критерием было сохранить читаемость тех мест, которые в принципе нужно читать. (Некие магические трансформации в реализации SHA1, например, читать смысла нету и причем сразу не было.)

И тут внезапно возник еще один интересный вопрос: а можно ли впихнуть клиент вообще на 1 страничку! Причем читаемым шрифтом, а не QR кодом, разумеется. С одной стороны, конечно, 500 строк. Но это довольно короткие строки, причем вместе с комментариями и пустыми строками. Плюс суммарно текста всего ~12 килобайт, по порядку уже сравнимо.

MySQL клиент формата A4Другими словами, давайте займемся обфускацией и отложим кирпич кода!!!

Буквально кирпич, с целью экономии драгоценных символов. Результат можно посмотреть вот тут, nanosql1.pdf (disclaimer: просто какой-то странный первый попавшийся в Гугле файлхостинг, тк. habrastorage не принимает PDF).

Немного повозивлся вручную, стало ясно, надо скрипт. Написал скрипт-обфускатор. (После него код надо дополнительно чуть править руками, но исправить в 3-4 местах код достаточно просто и скрипт исправлять уже лень.)

Ликвидация комментариев и пустых строк дает из ~10.5к примерно ~7.3к, длина строки кеглем 9 с узкими полями получается 105 символов на строку, кеглем 8 соответственно 118 символов, на страницу влазит порядка 60 строк. То есть просто убрать пустые места недостаточно хорошо, плюс видно, что всякие длинные идентификаторы в коде жрут много места. Дописал в скрипт еще несколько строк, чтобы исходные имена переменных типа m_iSock, m_pReadBuf менять на одно- и двухбуквенных p, q, a0 и так далее. Результат куда лучше, примерно 5.5к.

Сделать затем из поджатого кода «кирпич» язык C++ позволяет просто прекрасно! После замены идентификаторов граница нередко попадает куда-то, где можно просто взять и вставить перевод строки. А где нельзя (например, посередине слова printf) всегда можно поставить и перенести строку.

Большую часть листа при этом жрут директивы препроцессора и сделать с ними особо ничего нельзя, только минимизировать число #include и прочих #ifdef. Еще чуть поэкономить драгоценных строчек удается уже совсем частными трюками: скажем, подставляя вместо именованных констант #define MAX_PACKET 16777216 числа прямо в код, причем в виде (1<<24) для компактности. Трансформированный код в итоге почти можно отсылать на IOCCC, вот только надо отформатировать его вместо кирпича в ASCII картинку дельфина из логотипа MySQL.

Еще я пробовал трюки #define R return, #define V void итп. Нужного толку от них не получилось. Именно снизить число строк они не дают, слишком короткая программа. Ну хорошо, заменили 6 байт return на 1 байт R и так 20 раз, сэкономили на этом 100 байт или минус 1 длинную строку. И плюс 1 добавили #define-ом, в итоге толка нет.

Обфускация и окончательные урезания как обфусицированной, так и читаемой версии кода по строчке тут и там шли вперемешку. Уловки всё такие же; из интересного разве что переделка функций SHA1_Init() итп на класс (чуть экономим и строчки и токены) и затем chaining вызовов sha.Init().Update().Final() в 1 строчку вместо столбика в 3 строчки.

Итогом стала версия на 377 строк и вечер воскресенья, нужно было остановиться. ;) Наверное, можно дожать ее еще сильнее, добиться 300 строк, или там кирпича на 1 страничку девятым, а не восьмым кеглем, но это уже не очень интересно.

Итого.

Вполне работающий клиент MySQL удалось написать в 380 строк. Понятно, что он практически бесполезный, все равно везде есть куда более умный родной клиент. Но как пример «что можно написать за вечер и 400 строк» очень, считаю, показателен.

Обфусцированную версию клиента удается загнать в ~5 килобайт и уместить на 1 страничку формата A4. Это, конечно, чистое развлечение. Но зато на странный вопрос «Можно ли клиент MySQL уместить на A4» получен убедительный утвердительный ответ.

Статистика по тем 377 строкам получается следующая:

  • 34 строки #include, #define, void die();
  • 150 строк MysqlDriver_t: базовый сетевой буфер, разбор части пакетов (ok, error, field, row);
  • 74 строки SHA1_t: считалка SHA-1 для аутентификации (пакет auth);
  • 81 строка, начало main(): разбор опций, сетевое соединение, аутентификация;
  • 30 строк, конец main(): query loop, читаем запрос, отсылаем, получаем, печатаем ответ.

У кого не сошелся баланс, помните про пустые строки и комментарии.

Какая у этой басни мораль? А морали нет никакой, Наутилус прав. Но некие оргвыводы сделать можно. Например, что везде и всегда есть протухание и распухание кода, не стесняйтесь беспощадно выкидывать мусор. Например, что IOCCC доступен каждому. ;) Или, например, что вполне рабочий прототип сложного, казалось бы на первый взгляд, кода можно сделать удивительно быстро и удивительно компактно.

Последнее, на мой личный взгляд, главное. Пишите прототипы, это быстро и прикольно.

Автор: shodan

Источник

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


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