В предыдущих статьях (первая, вторая) было показано, как с помощью списков ввода-вывода сделать код более эффективным.
В его примере список ввода-вывода создаётся при помощи Enum.map:
Повторяющиеся строки заносятся в память только один раз. Создание одинаковых строк с помощью интерполяции ("<li>#{user.name}</li>") или конкатенации (<li>" <> user.name <> "</li>") потребует дополнительных операций по выделению памяти и копированию данных. В результате память будет использоваться менее эффективно, а у сборщика мусора появится много лишней работы.
Использование списков
Ознакомившись со статьёй, я непременно решил воспользоваться списками ввода-вывода в одном из своих открытых проектов на Elixir. Речь пойдёт о EventStore PostgreSQL для сохранности данных. Конструирование SQL-оператора для вставки большого количества событий осуществляется путём конкатенации и интерполяции строк. Так как в конечной строке получается очень много повторов, то здесь как раз и пригодится список ввода-вывода.
Поддержка Postgrex запросов со списками ввода-вывода
В EventStore Postgrex используется в качестве драйвера для взаимодействия с PostgreSQL. Таким образом, SQL-оператор insert превращается в функцию Postgrex.query/4. В документации к функции query/4 сказано, что её аргументом должны выступать данные ввода-вывода. Получается, что этим аргументом может быть и список ввода-вывода.
Итак, я подтверждаю, что запросы к PostgreSQL, состоящие из строки или списка ввода-вывода, выступающих в качестве SQL-оператора, при запуске кода в интерактивной консоли iex отлично справляются со своими задачами.
Реализация
Изменения в EventStore я внёс только в тело функции EventStore.Sql.Statements.create_events/1. Изначально SQL-оператор конструировался тем, что несколько событий помещались в insert с помощью интерполяции и конкатенации строк.
Теперь я сделал так, чтобы оператор составлялся с использованием вложенных списков ввода-вывода. Добавление элемента в список путём вложения – операция, которая выполнится за время O(1) и не потребует копирования данных.
Чтобы убедиться, что использование списков приносит свои плоды, я провёл несколько юнит-тестов.
Тестирование
В EventStore имеется тестовый набор бенчмарков, использующих Benchfella – инструмент для микробенчмаркинга в Elixir. Benchfella позволяет выбрать бенчмарк, запустить его несколько раз, сравнить результаты и представить их в виде графика.
Существующие бенчмарки покрывают большинство основных сценариев использования EventStore, то есть запись событий в поток и считывание их из потока. Набор бенчмарков включает и отдельные тесты для одного, 10 или 100 конкурентных процессов чтения/записи.
Ниже приведён пример бенчмарка для вставки 100 событий (сгенерированных до запуска теста) в EventStore.
Я сравнил время выполнения операций до и после внесения изменений, используя EventStore benchmark suite, чтобы убедиться в том, что производительность действительно стала выше.
Запуск бенчмарков
Чтобы обеспечить чистое окружение, перед запуском тестов нужно удалить и заново создать базу данных PostgreSQL.
Результаты бенчмарка ДО
Результат бенчмарка ПОСЛЕ
Сравнение результатов бенчмарков
В Benchfella имеется инструмент, который позволяет сравнить результаты двух последних запусков. Он показывает, как изменилась производительность между двумя запусками теста.
Можно видеть, что благодаря использованию списков ввода-вывода производительность возросла. После рефакторинга кода события добавляются в базу данных на 20-25% быстрее. Чтение событий происходит с прежней скоростью.
Использование списка ввода-вывода положительно сказывается на производительности. Если вы планируете организовать вывод данных и записать их в файл, то вот небольшой совет: забудьте о конкатенации. Воспользуйтесь списком ввода-вывода.
Один-два раза в неделю присылаем тёплые письма об Эликсире: переводы самых интересных статей до их появления в открытом доступе, анонсы событий и вкусные бонусы.
Обязательно подтверди почту, перейдя по ссылке в письме, иначе мы не сможем делиться с тобой полезностями.