Второй выпуск сетевого журнала
Практика функционального программирования, статья "История разработки одного компилятора".
Цитата:
Приоритетное направление деятельности нашей компании — разработка и внед-
рение решений, связанных с системами глобального позиционирования и навигации.
Основным продуктом является сервисная система для предоставления услуг монито-
ринга автотранспорта, ориентированная, в первую очередь, на операторов сотовой
связи.
При выборе ЯП для реализации как скриптого языка на контроллерах в финал попал и форт(написали на питоне в 2k строк кода, а контроллером был PIC18) - но далее он не прошел по причине отсутствия типизации и:
Цитата:
Еще одна проблема — наиболее компактный код на Форте получается, когда все вычисления проводятся на стеке. Но доступная глубина стека для вычислений в общем случае (не рассматривая Форты с командами типа pickn) ограничена приблизительно четырьмя его верхними ячейками. Более того, циклы со счетчиком организуются либо путем организации третьего «программного» стека для переменных циклов, что разрушает всю изящность языка, либо хранением переменной цикла в стеке адресов, что приводит к различным казусам в случае попытки возврата из слова,
вызываемого внутри цикла. Данная проблема приводит к нарушению двух важных требований сразу: безопасности и компактности байткода. Если для реализации некоторого алгоритма не хватает двух-трех переменных, то приходится прибегать к различным ухищрениям: использованию стека адресов для промежуточного хранения
данных, неуправляемой памяти — для хранения переменных, а также примитивов типа rot, swap или over. Это раздувает код и, в случае использования стека адресов, может приводить к непредсказуемым ошибкам времени исполнения, а также весьма затрудняет написание и прочтение кода. Как правило, чтобы понять некий алгоритм, реализованный на Форте, требуется его мысленно выполнить, что удаётся не всем.
Далее выбор пал на OCaml(и контроллер серии MSP430) и в итоге получили статическую типизацию и... :
Цитата:
Жёсткая типизация операций, например, арифметических. Если у нас есть целочисленные операции
+ − * /
то для того, чтобы пользоваться такими операциями для чисел с плавающей точкой, нам придётся либо ввести для них другие обозначения, например:
+. −. *. /.
либо сделать их полиморфными. При этом они станут бесполезны для выведения типов своих операндов, что сократит количество случаев успешного автоматического выведения типов и потребует большего количества аннотаций.
Можно ввести классы типов и применять другой, более сложный алгоритм выведения. С подобной проблемой сталкиваются и «большие» языки. При этом OCaml, например, выбирает наиболее простое решение — использование разных обозначений для таких операций. Тот же путь приемлем и для нашего языка.
... А чуть позже:
Цитата:
Поддержанию производительности компиляции на приемлемом уровне уделялось достаточно много внимания. Главной особенностью являлось периодически появлявшееся экспоненциальное поведение в самых разных местах компилятора. Оно проявлялось и при построении словаря, являясь просто алгоритмической ошибкой, но особенно много головной боли доставила система вывода типов.
Унификация — не самый дешевый в смысле производительности алгоритм, так что важно поддерживать количество данных, участвующих в унификации, минимальным и не допускать повторных вычислений того, что уже вычислено.
Этот момент стоит отметить особо, так как большую проблему с производительностью создала реализация полиморфных функций. Дело в том, что если мы полностью вычисляем типы для каждого выражения каждый раз, как оно встречается, то реализация полиморфных функций тривиальна — мы просто используем выведенные значения типов параметров и возврата полиморфной функции в том выражении, где мы её используем, не сохраняя значений для вычисленных типов. Функция действительно получается полиморфной, её тип различен в разных контекстах. Но перевычисление всех типов для каждого выражения приводит к абсолютно неприемлемой вычислительной сложности. Чтобы этого избежать, пришлось пойти на достаточно нетривиальные меры.
В остальном обычный профайлинг и не очень агрессивная мемоизация позволили удержать производительность на уровне, неформально определённом нами как «менее секунды на любом файле, который в состоянии написать человек». Это означает, что как только на каком-либо скрипте время компиляции превышает секунду, берётся в руки профайлер, и эта проблема устраняется. До сих пор это получалось.
.....
В целом производительность компилятора следует признать удовлетворительной, хотя до идеала далеко — в частности, компилятор OCaml работает существенно быстрее.
После долгих экспериментов получили нечто приближенное к си:
Код:
def main() {
putsn(”GPS ON”);
gps_power(true);
while true {
local nmea = collect_gps_data();
if nmea.fx > 0 then {
putsn(”Coords fixed”)
puts(”Satellites: ”);
putsn(utoa(nmea.gps_sat, 16));
puts(”Latitude: ”);
putsn(nmea.gps_lat);
puts(”Longitude: ”);
putsn(nmea.gps_lat);
}
}
}
И вот такие итоги:
Цитата:
Разумеется, Бип был разработан вовсе не как фан-проект для обучения написанию компиляторов. И его предтеча, Форт, и он сам применялись в разрабатываемых системах, даже будучи не совсем стабильными, развиваясь параллельно с основными системами.
Основные цели, которые ставились при разработке Бипа, были достигнуты, а по отдельным параметрам он даже превзошел связанные с ним ожидания.
Разрабатывать скрипты на нем оказалось гораздо быстрее и проще, чем, например, писать код на Си, что привело к тому, что часть логики работы устройства, которая изначально планировалась к реализации в прошивке, реализуется в скрипте.
.....
Еще один немаловажный аспект: байткод Бипа плотнее, чем машинный код, генерируемый компилятором Си. В то время, когда ресурсы code memory, отведённые под прошивку (около 32 КБ flash), практически исчерпаны, 8 КБ сегмента, отведённого под байткод, еще имеют резервы. В связи с этим нехватка функциональности прошивки
вполне может быть компенсирована за счет скрипта.
.....
Удалённое обновление скрипта уже многократно использовалось при пользова-
тельском тестировании, позволяя устранять различные проблемы на лету, практиче-
ски между двумя событиями трекинга, менее чем за 30 секунд. Пользователи даже не
замечали, что произошло что-то особенное.
Стоит упомянуть, что принятые в дизайне языка и рантайма решения — строгая статическая типизация, отсутствие неинициализированных переменных, отсутствие рантайм-исключений — полностью окупились: за полгода пользовательского тестирования не зафиксировано ни одного падения прошивки устройств, вызванного дефектами дизайна рантайма или компилятора. При этом не потребовалось разработки эмуляторов и длительного тестирования скриптов на них — скрипты пишутся сразу и тестируются непосредственно на самих устройствах. Благодаря типизации есть уверенность, что скомпилировавшийся скрипт будет нормально работать и не приведет к потере связи с устройством, что не может быть гарантировано при использовании, например, Форта или гипотетических динамических языков.
Цитата:
Количество кода живых проектов имеет тенденцию постоянно увеличиваться.
Чтобы этот рост контролировать, необходим рефакторинг, который, в свою очередь,
требует плотного покрытия кода тестами, требующими времени на написание и под-
держку.
Невозможно переоценить, насколько упрощается задача рефакторинга в случае применения языка с сильной типизацией.
Рост размера кода, увеличение его структурной сложности, количества предположений, соглашений и взаимосвязей, уменьшение его понятности — очень существенные негативные факторы, которые нельзя игнорировать.
Цитата:
Опыт применения Python выявил еще одну интересную тенденцию: переписывание сложных или проблемных участков кода в функциональном, иммутабельном стиле зачастую приводило не только к правильному функционированию кода, чего не получалось добиться от его императивного аналога, но и к сокращению его размеров.
В таком случае, зачем пытаться использовать функциональный подход в языках, которые его не поощряют, если есть функциональные языки? Языки, которые являются более безопасными в силу типизации, имеют дополнительные возможности и полноценные оптимизирующие компиляторы, генерирующие код, минимум на порядок превосходящий по производительности упомянутые динамические языки.
Последний фактор оказался весьма важен, так как скорость компиляции — это заметный фактор, влияющий на использование языка.
Принимая во внимание усилия, которые пришлось (и еще придется в дальнейшем) приложить для достижения приемлемого для работы времени компиляции, можно констатировать правильность этого выбора.
Ох, что-то я разошелся
Хотел привести линк и в паре слов описать содержание, а в итоге получилось нечто не совсем определенное...