понедельник, 22 декабря 2008 г.

SXEmacs - лучший программный продукт года

Решил немного помечтать и вообразить, что нужно SXEmacs, чтобы выиграть приз «Лучший программный продукт года». Было ли вообще такое, чтобы GNU Emacs выигрывал этот приз? Что же необходимо изменить и сделать в SXEmacs, чтобы это событие произошло?

  • Кардинально улучшить подсистему обработки событий (event loop).
  • Реализовать подсистему отображения (redisplay) используя современные средства, например Cairo. Использовать современные библиотеки виджетов, например GTK+.
  • Реализовать нормальную мультиязыковую поддержку.
  • Поймать все утечки памяти при использовании BDWGC и сделать его сборщиком мусора по умолчанию.
  • Реализовать чистую интеграцию различных ЯП, таких как Python, Ruby и др.
  • Улучшить документацию как для разработчиков так и для пользователей.
  • Создать более продвинутый и быстрый сопоставитель текста с образцом на замену регулярных выражений. На базе него сделать, чтобы font-lock работал корректно и быстро.
  • Создать систему совместной разработки на базе SXEmacs.

info

Призываю читателей к дискуссии. Опишите свой взгляд, расскажите каким вы хотели бы видеть ([S]X)Emacs.

Подробное изложение каждой части этого плана я буду публиковать отдельными постами (не обязательно в правильном порядке). Потом их можно будет склеить в одну статейку и оформить как некий путь развития SXEmacs. Сегодня будет небольшой обзор подсистемы событий.

Подсистема событий

Самое удивительное, что сейчас вообще нет этой подсистемы ;). Есть определённые части, выполняющие необходимые действия, но отсутствует целостность, расширяемость и сила. В существующей ПС есть только высокий уровень доступа, нет разделения API на низкий/высокий уровни, поэтому если требуется что-то добавить, то необходимо делать много низкоуровневой мишуры, с помощью копи&паст программирования — утомительно.

В SXEmacs было решено использовать libev как низкоуровневую базу для ПС. Выбор стоял между liboop, libevent, libev и одной проприетарной библиотекой, которую планировалось перелицензировать (уже было получено согласие правообладателя). liboop отпала первой — библиотека практически мертва. libevent является почти de-facto стандартом для создания софта с ПС, но у нескольких разработчиков SXEmacs был негативный (как и позитивный тоже) опыт использования libevent и как раз ограничения, которые существуют в ней плюс просто ошеломляющие результаты тестирования скорости libev в сравнении с libevent не позволили выбрать libevent. Проприетарная библиотека отпала так как она зависела от дополнительной большой библиотеки, а так же в ней не очень удобный API (хотя, видимо, очень мощный) и отсутствовала документация.

Так зачем вообще всё это нужно? А для того, чтобы полностью избавиться от внутренних блокировок в SXEmacs. Блокировки бывают двух типов:

  1. Выполнение elisp кода приостанавливается в ожидании какого-то события.
  2. Блокирует сам вызов библиотечной функции.

К пункту 2 относятся вызовы для разрешения DNS имён, такие как gethostbyname(3) и getaddrinfo(3), вызов connect(2), а также, как это не удивительно, вызов write(2). В GNU Emacs реализован неблокирующий вызов connect если передано ключевое слово :nowait1 в процедуру make-network-process, при этом вызов make-network-process всё же может заблокировать, если заблокировало разыменование имени хоста. В SXEmacs вызов connect всегда блокирует, поэтому open-network-stream всегда блокирует. Что касается write(2), то по настоящему всё же write не блокирует, ибо дескриптор был переведён в неблокирующий режим для неблокирующего чтения, но все Емаксы, получая EWOULDBLOCK от write(2), делают select в ожидании, когда разрешат записать.

К пункту 1 относятся любые вызовы accept-process-output и call-process. Что произойдёт если accept-process-output ожидает данных и в этот момент приходит какое-нибудь событие? Если событие системное, то оно обрабатывается немедленно, если событие специальное или командное2, то оно будет отложено и записано в очередь событий, которые будут обработаны позже.

Асинхронный обработчик событий

В SXEmacs есть такая вещь называется ASYNEQ — это возможность асинхронной обработки событий. Не всякое событие может быть обработано асинхронно. В общем случае асинхронная обработка командных событий ведёт к условиям гонки (race condition)3. Специальные же события без проблем можно обрабатывать асинхронно. Именно по этой причине xlib и xwem работают более предсказуемо под SXEmacs, ибо они порождают просто тонны специальных событий, которые в XEmacs обрабатываются синхронно как и командные.

Часто командные события всё же можно обрабатывать асинхронно. Условия гонки были обнаружены токмо спустя год после того как в SXEmacs появился ASYNEQ. Это говорит о том, что условия гонки крайне редки и сильно зависят от специфики elisp кода, то есть автоматически невозможно определить возникнет ли в конкретном случае race condition или нет. Возможно, необходимо определить переменную asyneq-ignore-command-events, которую можно связать со значением t в особых случаях, когда необходимо запретить асинхронную обработку командных событий.

Как организовать новую ПС

Что мы хотим от новой ПС:

  • Целостность — одно место обработки событий, отсутствие костылей на вроде как при потенциальном блокировании write в process-send-string
  • Простой и мощный API как с низкоуровневыми, так и с высокоуровневыми конструкциями
  • Возможность интеграции других подсистем событий в ПС SXEmacs. Для начала X11 и Glib
Схема ПС

Сх. 1. пример работы ПС SXEmacs (event-loop.svg)

Использовать новую ПС будут:

  • На низком уровне
    • Асинхронное разыменование DNS
    • Остальные потенциально блокирующие вызовы
    • Клей для других ПС
  • На высоком уровне
    • Event API на elisp уровне
    • Таймеры
    • Прогнозируемые предикаты
    • И т.д.

Попробую изобразить в виде схемы, что будет происходить при возникновении командного события, которое порождает вызов open-network-stream. Как видим, событие помещается в очередь событий и когда до него дойдёт очередь, оно будет обработано, что породит вызов процедуры open-network-stream, которой, в свою очередь, необходимо разыменовать имя хоста и подсоединиться к нему, т.е. произвести две потенциально блокирующих операции. За время выполнения open-network-stream мы два раза посетим обработчик событий и в случае, если нам действительно нужно будет ждать, мы сможем обработать ещё кучу событий из очереди.

Именно так я и вижу работу новой ПС в SXEmacs.


1Вроде RMS был ярым противником ключевых слов
2Командные события порождаются действиями пользователя (нажал кнопку, мышкой побаловался и т.д.)
3Не буду описывать подробности, но, поверьте мне, я думал что помешался когда наблюдал эти условия гонки при работе с flyspell. Впрочем, психологическую составляющую этого опыта можно вынести отдельным постом, будет интересно

ReST source Скачать оригинал

4 комментария:

Alex Ott комментирует...

ну второй, третий и предпоследний пункты есть в gnu emacs.
насчет поддержки разных языков - я все-таки надеюсь на работы, ведущиеся в рамках cedet

lg комментирует...

А что, GNU Emacs разве умеет использовать Cairo для redisplay? Я думал там только виджеты GTKшные поддерживаются

насчёт предпоследнего можно по подробней? Что это за механизм?, даёт ли он O(N) скорость? Я сомневаюсь, что в Emacs сделают regexpы со скоростью O(M*N), где M - длина регекспа, а N - длина входа .. хотя документов о том как это сделать предостаточно

вообще как раз про предпоследний пункт я и хочу написать в след раз как продолжение серии bsoy .. там и можно будет обсудить все перипетии

Alex Ott комментирует...

насколько я помню - вроде делали поддержку Cairo. В 23-й версии переписали display часть, так что шрифты теперь смотрятся хорошо. Плюс народ вел работу над gtkembed, что позволяло ембедидить практически любые Х-вые программы.
Насчет регекспов. При переходе на 22-ю версию, regex engine был полностью переписан, что дало очень сильное увеличение быстродействия, и фонт-лок сейчас работает совсем незаметно, даже на огромных исходниках

lg комментирует...

отлично! быстрые регекспы это правильно!

хорошие изменения, нужно будет поковырять GNU Emacs 23 на досуге