Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Фреймворк Zotonic

Глава 9 из книги "Производительность приложений с открытым исходным кодом".

Оригинал: Zotonic
Авторы: Arjan Scherpenisse и Marc Worrell
Перевод: Н.Ромоданов

Виртуальная машина Erlang

В виртуальной машине языка Erlang есть несколько свойств, которые важны с точки зрения производительности.

Процессы являются недорогим ресурсом

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

Процессы являются недорогим ресурсом не только с точки зрения их запуска, но также и с точки зрения потребления памяти - 327 слов на процесс, что эквивалентно ~ 2,5 КБ на машине с 64-разрядной архитектурой [6]. Сравните это с ~ 500 КБ для языка Java и с используемых по умолчанию 2 Мб для потоков pthreads.

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

Копирование данных является дорогостоящей операцией

Сообщения между процессами являются в виртуальной машине языка Erlang относительно дорогостоящими, поскольку сообщение копируется внутрь процесса. Такое копирование необходимо из-за наличия сборщика мусора, используемого для каждого процесса в Erlang. Важно предотвращать копирования данных, и именно поэтому в деп-кэше фреймворка Zotonic используются таблицы ETS, доступ к которым возможен из любого процесса.

Отдельная куча для больших байтовых массивов

Есть важное исключение, связанное с копированием данных между процессами. Массивы байтов, размер которых больше 64 байта, не копируются между процессами. Для них есть своя собственная куча и отдельно работающий механизм сбора мусора.

Это позволяет удешевить пересылку больших массивов байтов между процессами, т.к. копируется только ссылка на массив байтов. Тем не менее, это усложняет сборку мусора, поскольку прежде, чем можно будет освободить массив байтов, нужно утилизировать все ссылки.

Иногда передаются ссылки на части большого массива байтов: большой массив байтов не может быть утилизирован до тех пор, пока не будут утилизирована ссылка на его небольшую часть. Из этого следует, что копирование массива байтов будет оптимизацией в том случае, если при этом освобождается большой массив байтов.

Обработка строк является дорогостоящей операцией

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

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

В языке Erlang есть свой собственный популистский ответ строкам: списки ввода/вывода (io-lists). Списка ввода/вывода являются списками, в которые вложены списки, целые числа (однобайтовые значения), массивы байтов и ссылки на части других массивов байтов. Списки ввода/вывода чрезвычайно просты в использовании и добавление данных в хвост, в начало или в середину списка стоит недорого, т. к. для всех этих операций нужны лишь изменения сравнительно коротких списков, не требующих какого-либо копирования данных [7].

Список ввода/вывода можно в том виде, как он есть, отправить в «порт» (дескриптор файла), в котором происходит выравнивание структуры данных в поток байтов и отправка его в сокет.

Пример списка ввода/вывода:

[ <<"Hello">>, 32, [ <<"Wo">>, [114, 108], <<"d">>].

Который преобразуется (выравнивается) в массив байтов:

<<"Hello World">>.

Интересно, что обработки строк в веб-приложении используется, в основном, в следующих ситуациях:

  1. Объединение данных (динамическое и статическое) в результирующую страницу.
  2. Удаление кода HTML и очистка контента.

Списки ввода/вывода языка Erlang являются идеальной структурой данных для первой ситуации. А вторая ситуация решается при помощи применения агрессивной очистки контента перед тем, как контент сохраняется в базе данных.

Если это объединить, то это означает, что для фреймворка Zotonic отображаемая страница просто представляет собой объединение в один список ввода/вывода большого количества массивов байтов и предварительно очищенного контента.

Использование фреймворка Zotonic

В фреймворке Zotonic интенсивно используется сравнительно большая структура данных, называемая Контекст (Context). Это запись, в которой содержатся все данные, необходимые для оценки запроса. В ней хранятся:

  • Данные запроса: заголовки, аргументы запроса, данные тела запроса и т.д.
  • Статус веб-машины Webmachine
  • Информация о пользователе (например, идентификатор пользователя, информация правах доступа)
  • Языковые предпочтения
  • Класс User-Agent (указывающий, например, текстовый режим, телефон, планшет, настольный компьютер)
  • Ссылки на специальные процессы сайта (например, уведомления, деп-кэш и т.д.)
  • Уникальный идентификатор обрабатываемого запроса (он станет идентификатором страницы)
  • Идентификаторы сессии и процесса страницы
  • Процесс подключения к базе, используемый во время транзакции
  • Накопители данных, используемых для ответа (например, данные, действия, выполняемые при рендеринге, файлы JavaScript)

Все эти данные могут образовывать большую структуру данных. Отправка такого большого контекста в различные процессы, работающие с запросом, могла бы привести к существенным накладным расходам, связанным с копированием данных.

Именно поэтому мы стараемся выполнить большую часть обработки запроса в едином процессе: в процессе Mochiweb, который получил запрос. Дополнительные модули и расширения вызываются при помощи обращений к функциям, а не с помощью межпроцессных сообщений.

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

Также нужно, чтобы Zotonic отправлял сообщение в том случае, когда происходит рендеринг кэшируемых подшаблонов. В этом случае перед тем, как Контекст будет передан в виде сообщения в процесс, осуществляющий рендеринг подшаблонов, в Контексте удаляются все результаты использования промежуточных шаблонов, а также некоторые другие ненужные данные (например, информация о регистрации).

Нас не интересует насколько большим при передаче сообщений будут массивы байтов, хотя они, в большинстве случаев, будут более 64 байтов, поскольку они не будут копироваться между процессами.

При обработке больших статических файлов имеется возможность использовать системный вызов Linux sendfile(), который делегирует операционной системе операцию отправки файла.


Продолжение статьи: Изменения в библиотеке Webmachine.