пятница, 9 августа 2019 г.

Просто о сложном. О жизни и оптимизации. И получении от этого драйва и удовольствия

А ещё я ускорил работу нашего хранилища. И его копирование вместо 3х НЕДЕЛЬ (!) стало занимать 4 часа.
Поправил буквально 5 строчек кода. Но это вряд ли интересно.
Алгоритм маляра конечно же.
Я бы написал на эту тему. Но это вряд ли уже интересно.

На самом деле оптимизацией нашего хранилища я вплотную занимаюсь уже почти год.
Назрела необходимость. Да и потенциальные объёмы данных выросли.

Плотно занимаюсь хранилищем. Ну кроме других задач.

Ну и всё это время, что я занимался хранилищем меня преследовала мысль, что я занимаюсь прокрастинацией.

Тут тесты пописал... Тут замеры сделал.. Тут тесты пописал... Тут замеры сделал...
Тут съэкономил на спичках. Опять замеры сделал.
Тут тесты пописал... Тут замеры сделал.. Тут тесты пописал... Тут замеры сделал...
Тут съэкономил на спичках. Опять замеры сделал.

И так по кругу... Конца и края не было видно...
Что-то беспросветное. "Мартышкин труд"...

Руки уже опускались, хотелось пойти и "переквалифицироваться в управдомы". Или "записаться в оппозиционеры".

Хорошо, что хоть как-то другие задачи отвлекали и развеявали грустные мысли о собственной профнепригодности.

Но было крайне грустно. Прям "жить не хотелось". Прям даже думал, что "выпал из профессии навсегда". Глупостью какой-то занимаюсь.

Хорошо хоть другие задачи были. Более успешные.

"Но потом, по прошествии года, как венец моего исправленья" (крепко сбитый литой монумент) на меня сошло несколько "озарений". Банальных до жути.

Нашёл я сначала два "маленьких" алгоритма маляра. И дело сдвинулось с мёртвой точки.

Производительность выросла. Немного, но выросла. И главное  - я написал несколько тестов, которые сначала показывали экспоненциальную зависимость в сложности, а после переделок стали показывать линейную зависимость или даже логарифмическую.

Жизнь заиграла новыми красками. Стало "интересно". Я уже не ощущал прокрастинацию, а понимал, что есть движение вперёд. "Огонь и движение". Каждое новое изменение кода улучшало ситуацию. Пусть немного, но улучшело.

Но "радость побед" была ещё впереди.

И тут "по наитию" я написал один "крайне неудобный тест", который работал с хранилищем крайне неоптимальным образом. И на реальных данных его выполнение затянулось аж на ТРИ НЕДЕЛИ (!) вместо изначально прогнозируемых четырёх часов.

При этом экпоненциальная сложность в итоге была на лицо.

В первый раз тест за ТРИ НЕДЕЛИ так в итоге и не закончился, а всё ухудшал и ухудшал свой прогноз.

После некоторых раздумий над результатами этого теста я ещё немного "съэкономил на спичках".

Переписал наши константные строки. Сделал их реально константными. С возможностью контроля за несанкционированным доступом к памяти константной строки (это тема реально для отдельной статьи, там VirtualAlloc используется с защитой страниц от записи).

Попутно ещё избавился от Unicode где это только возможно. Но это мелочи. Но это тоже тема для отдельной статьи вообще-то. Как работать с Unicode не теряя на объёме данных.

Попутно написалось ещё несколько "неудобных тестов", которые дали дополнительную пищу для размышлений.

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

После этого я ещё раз сильно поразмыслил над результатами тестов.

Понял для себя несколько банальных вещей. Типа что:

1. При прочих равных - списки лучше чистить не от начала к концу, а наоборот - от конца к началу.
2. Далеко не всегда для списков хорошо работает поиск делением пополам. Особенно для "коротких". Хотя у Кнута это как раз явно написано.
3. При вставке в список чаще всего надо начинать не с середины, а с трёъ реперных точек:
 а. Место последней вставки.
 б. Начало списка.
 в. Начало списка + 1.
 г. Конец списка.
4. С поиском в "длинных списках" - такая же ситуация. Прежде чем начинать бинарный поиск - лучше сначала проверить указанные выше реперные точки. Что в общем тоже - "очевидно".

По результатам этих раздумий и переделок - ситуация опять улучшилась. Динамика была положительной. "Огонь и движение".

Потом "неожиданно" я понял, что FileLock не добавляет скорости к совместному доступу к файлу, и переписал таким образом, что если потоки хранилища доступны лишь на чтение, то FileLock не используется.

Пришлось ввести понятие версионности потоков. Все потоки либо совместно читаются, либо новая версия пишется эксклюзивно "в сторону" и про неё другие пользователи "не знают", пока она будет не дописана. Когда она дописана - она объявляется главной и константной. И далее пользователи начинают совместно читать из неё.

Потом я опять "неожиданно понял", что FileFlush опять же скорости не добавляет, а он у меня использовался повсеместно там где потоки работали за запись.

И тут я сделал некий "финт ушами". Я стал заранее распределять коллекцию кластеров для данного конкретного пользователя. Получился кеш кластеров. Кластеры из этого кеша стали использоваться при работе с потоками на запись. FileFlush стал дёргаться сильно реже. Только когда версионные потоки, о которых выше было сказано, объявлялись константными.

Это ДРАМАТИЧЕСКИ улучшило ситуацию. "Неудобные тесты" стали отрабатывать не за три недели, а за сутки.

Стало прям "хотеться жить". Динамика была крайне положительной. "Огонь и движение".

Потом я сделал для себя ещё один банальный вывод, что хеширование элементов  длинных списков - сильно сказывается на улучшении производительности и сделал соответствующие модификации кода. Ну и написал ещё пачку тестов.

Динамика была более чем положительная.

Но тут нашёлся ещё один "неудобный тест". Он тоже показывал экспоненциальную зависимость.

Но с ним оказалось всё просто. Время от времени он "зависал" (переставал бежать прогресс-индикатор) и уходил в себя.

Но тут реально было всё просто. Надо было лишь в такие моменты нажать в отладчике на паузу и посмотреть - в каком же коде крутится тест.

Всё было более чем банально.

У нас потоки в основном состоят из кластеров фиксированного размера, но если поток не кратен кластеру, то при записи его на диск используется сабкластеризация. Из кластера выделяется "хвостик", который используется для потока, а остаток от кластера складывается в список сабкластеров для последующего использования.

И тут оказалась одна вещь, которая на само деле лежит на поверхности. В итоге в список сабкластеров очень много складывается "маленьких хвостиков" от 1 до 20 байт. Их реально много. На реальной базе документов в 100 Гб их накапливалось порядка 600 тыс штук. При этом они были практически невостребованны.

При этом они участвовали в КАЖДОЙ процедуре распределения нового "хвостика".

Ну решение было опять же крайне банально. Количество таких "мелких хвостиков", которое попадает в кеш повторного использования было ограничено заранее заданным маленьким значением "взятым от фонаря". Все остальные "маленькие хвостики" просто выкидывались. Типа "накладные расходы".

И тут время работы "неудобного теста" уменьшилось с суток до четырёх часов.

При этом размер базы документов за счё этих накладных расходов вырос всего лишь на 1.5%. Чем в общем-то можно пренебречь.

В итоге я получил ДРАМАТИЧЕСКОЕ ускорение работы с хранилищем в "крайне неудобных тестах". Собственно набор тестов "на все случаи жизни". Пищу для дальнейших размышлений. Ну и самоудовлетворение. И надежду на то, что "в управдомы" идти ещё рано.

Вот как-то так. Если будет интересно и будет вдохновение - позже опишу технические детали вышеописанного процесса.

Что-то возможно я ещё забыл.

Да, попутно я выяснил, что служебные потоки вложенных подхранилищ (в которых хранятся элементы FAT для вложенных элементов) должны прирастать не просто какой-то дельтой, а степенями двойки. Это тоже хорошо сказалось на производительности. Но это уже в принципе "мелочи".

Да и не надо писать в комментариях про "изобретение велосипеда на коленке". Мне за это деньги платят. И осознанно используются СВОИ решения, а не стороннние. Хотя и сторонние решения тоже используются.

Ну и в итоге я обогатился пищей для дальнейших размышлений. Например о работе со списками и прочими контейнерами. Типа всяких "годных лайфхаков", которые работают в некоторых частных случаях. Но достаточно распространённых для ограниченного круга задач.

Ну и ещё я очередной раз для себя лично уяснил крайнюю полезность immutable-объектов. И то что их константность очень хорошо сказывается на общем доступе и его производительности.

В итоге - "поправил 5 строчек кода" - это результат почти годовой работы и анализа.

Комментариев нет:

Отправить комментарий