Непрерывное компилирование
Вместо предисловия
Вспомнил об ещё одной очень старой заметке, которую я писал ещё в 1995 году для журнала «Монитор» (но журнал к тому времени уже закрылся). Речь идет уже не о языке и трансляторе, а об IDE.
В режиме фоновой работы транслятора я поработал с год и был им очень доволен. К сожалению, переход на Windows прекратил дальнейшее развитие этого режима, хотя на современных средствах реализация чего-то подобного будет гораздо проще.
Собственно статья
Так чем же занимается компьютер, когда Вы редактируете свою программу?
Да, собственно, ничем. 99.99% времени уходит на ожидание нажатия клавиши (разумеется, если Ваш PC работает в обычном однозадачном режиме). А затем компьютер кинется компилировать сделанное Вами, и теперь уже Вы будете ждать его, ругая за медленное компилирование, что, честно говоря, несправедливо — он ждал Вас минуты, а Вы ждете лишь несколько секунд или десятки секунд.
Вот если бы провести компиляцию, в то время как Вы размышляете над текстом программы, но, увы — в тот момент текст ещё не готов и трансляция его бессмысленна. Есть только один промежуток времени с момента как Вы нажали последнюю клавишу редактирования и до нажатия клавиши записи на диск, выхода из редактора и т.п., когда можно было бы попробовать провести компиляцию.
Однако только Вы знаете, что это и была последняя редакция текста, а компьютер об этом узнать никак не может. Но поскольку он все равно ничего не делает между нажатиями клавиш — пусть считает каждую клавишу последней и непрерывно работает!
Таким образом, складывается следующая схема: когда текстовый редактор ждет нажатия клавиши, он запускает компилятор, считая имеющийся сейчас в памяти текст полностью готовым. Компилятор работает до первой ошибки или до первого нажатия (любой) клавиши или, естественно, пока не оттранслирует программу целиком. Если же во время работы компилятора нажимается клавиша, результаты его работы просто пропадают. Весь расчет на то, что обычно после внесения исправления Вы проверяете и осмысливаете свои действия и этого времени хватит на то, что бы проверить хотя бы наличие синтаксических ошибок в программе. Естественно также, что компилятор запускается, если нажатые клавиши внесли изменения в текст, если же это было просто движение курсора или листание, а текст уже успели оттранслировать, его незачем транслировать повторно, как это делалось на старых ВЦ для улучшения показателя "среднесуточная загрузка машины".
Для проверки этой идеи был взят обычный текстовый редактор SideKick и компилятор с языка PL/1 (поскольку автор этой статьи занимается помимо всего прочего сопровождением этих программ и часто их меняет). Потребовалась также хорошая машина (486 DX2), так как компиляция проходит довольно долго и кроме этого, нужно было иметь нормальное ОЗУ (16М), что бы хранить там сам компилятор, стандартные файлы, вызываемые директивой INCLUDE и т.п. и не обращаться без конца к жесткому диску.
Что же показал двухмесячный опыт работы в этом режиме? Эффект есть лишь в том случае, когда человек привыкает к такой работе и перестает обращать внимание на непрерывное компилирование. Индикация была специально сделана малозаметной и не отвлекающей — в верхнем правом углу экрана вместо псевдографического символа рамки появляется символ "К" — идет компиляция, "+" — компиляция прошла, ошибок не найдено, "-" — были ошибки.
Если были ошибки — можно, нажав клавишу, вызвать окно с сообщением об ошибке на экран (как в обычной интегрированной среде).
Удобно использовать индикатор при расстановке скобок и парных ключевых слов типа BEGIN — END. Редакторы интегрированных сред тоже оперативно обрабатывают текст программы — выделяют ключевые слова цветом, подсчитывают скобки и т.д., однако в более сложных случаях, например в файле, включаемым в данный текст с помощью оператора INCLUDE, уже есть такой идентификатор как и в Вашей программе, они не помогут сразу определить ошибку, что и понятно — ведь для этого в редактор потребуется встроить фактически полный компилятор!
В данном же режиме работы это определяется легко и очень оперативно, синтаксические ошибки находятся компилятором быстро, практически мгновенно после каждого исправления. Это позволяет немного изменить сам стиль работы за компьютером. Раньше правильнее было вносить по одному исправлению в программу и затем проверять трансляцией, чтобы не запутаться в нескольких исправлениях и чтобы избежать влияния одних исправлений на другие. Теперь же все исправления вносятся в текст сразу, при этом лучше просто делать небольшую паузу после очередного логически законченного изменения и (скосив глаз) убедиться, что ошибок не возникло. Экономия времени и количества нажатых клавиш налицо. Однако и здесь есть один прокол: если между нажатиями клавиш компилятор успел таки довести трансляцию до конца или до первой ошибки, то компьютер опять ничего не делает и только ждет клавиши. Чем же теперь его занять, а?
P.S. Вдогонку к заметке: в чем-то подобный подход реализован у нас и при вызове редактора связей. Он может автоматически вызываться после работы компилятора. Если компилируется не главный модуль, то вызывать редактор бессмысленно — он не знает где искать главный, да и главный может быть вообще ещё не готов. А вот если компилируется главный модуль, то может быть два случая: больше модулей уже не нужно (кроме указанных библиотек) или будут ещё какие-то модули. У нас в любом случае вызывается редактор, но если он не находит каких-то связей, то он на экран ничего не выдает и делает вид, что вообще не вызывался.
Таким образом, в частых случаях единственного и главного модуля сразу получается выполняемый файл без явного запуска редактора связей, в остальных случаях нужно явно вызывать редактор отдельным действием.
Автор: Д.Ю.Караваев. 1995 г.
Опубликовано: 2018.08.26, последняя правка: 2019.01.28 20:39
Отзывы
✅ 2018/09/20 19:36, Автор сайта #0
Чем занять компьютер? Ответ очевиден: пусть он для Вас майнит биткоины. А будет Ваш компилятор с IDE стоять на тысячах компьютеров да на Вас добывать криптокопеечку — глядишь — и на работу ходить не надо. Знай себе только работай над компилятором с IDE, чтоб биткоины не иссякали :) С Вас причитается за идею :)✅ 2018/11/03 22:43, rst256 #1
Лучше отслеживать в редакторе расположение отдельных функций, определений типов и классов и их зависимости. Тогда нужно будет перекомпилировать только те функции, которые были изменены. И передавать компилятору надо не текст, а, как минимум, уже готовые токены. Ведь для подсветки синтаксиса уже и так надо лексический анализ выполнять? Вот и пусть его встроенный лексер делает.
В идеале вообще надо ещё и через парсер сразу код прогонять и держать всегда актуальное АСТ в памяти IDE. Тогда при редактировании исходного текста программы достаточно будет обработать только те узлы АСТ, исходный текст которых был изменен. И если анализ не выявил ошибок в коде, то запускать компиляцию измененной функции или её части.✅ 2022/06/09 22:33, void #2
Любопытно. Читал в Википедии про "Atari Assembler Editor" — картридж с ассемблером и монитором для CPU 6502. Эта же компания разработала ранее Atari BASIC, вот и этот "Assembler Editor" содержал редактор + токенайзер + компилятор/интерпретатор в духе этого Бейсика (например, у этого ассемблера были номера строк, простенький макроязык, монитор и отладчик для редактирования/запуска). Изначально этот "Assembler Editor" получился чуть ли не самой медленной реализацией ассемблера 6502 из имеющихся на тот момент (как и их реализация Бейсика). Затем он был доработан: следующие версии EASMD, MAC/65, BUG/65 развивала другая фирма (от тех же основателей), "Optimized Systems Software", язык Action! (упрощённый Алгол вроде Cowgol).
MAC/65, в свою очередь, был одним из самых быстрых ассемблеров для Atari (под 6502) — получилось действительно Optimized. Почему? Работал инкрементальный парсер/токенайзер (аналогично бейсиковскому).
После того, как отработал ввод и парсер — из токенизированного представления, если уже парсинг и компиляция (одной только новой строчки) прошла без ошибок — кодогенератору и линкеру оставалось только подключить готовый токенизированный код, инкрементально пропатчив существующее.
А вообще, чем сложнее язык — тем больше в нём сложных высокоуровневых структурных выражений, в смысле first class objects этого языка. Поэтому по идее нужно что-то вроде структурного редактора и инкрементально парсить последнее охватывающее правки структурное выражение, охватывающее последнее изменённое, его AST/CST/макросы.
Например, авторы Plan9 изобрели текстовый редактор sam и написали статью про структурные regexp-ы. Sam — это редактор с командным языком вроде ed, только визуальный, а не командно-строчный. У него есть область по умолчанию, которая редактируется и к которой применяются команды.
Ещё например, наподобие редактора vim есть kakoune. В kakoune есть multiple cursors, multiple selection. Идея в том, что в "визуальном", но модальном vim есть нормальный командный режим, и режим вставки. Командный режим в Vim можно понимать как Verb-Object, то есть: сначала указывается что делать, потом с чем.
Командный нормальный режим + режим вставки в vim сами по себе не вполне логичны. В kakoune предлагают:
1) большую ортогональность, то есть разделение труда между командным и режимом вставки,
2) более логичный язык, то есть: командный режим в kakoune можно понимать как Object-Verb, то есть сначала указывается с чем делать, потом что,
3) более логичные переходы между структурными выделениями. То есть, есть например весь текст — всё выделение. выделили например блок (подвыделение этого всего текста), в нём поискали регексп. выделились все подходящие вхождения (подвыделение этого блока), их сразу одной командой заменили на замену.
Вот пример полезности "multiple selections", "multiple cursors".
В целом, идея про инкрементальный парсинг, непрерывный lint/макроподстановки/компиляцию/сборку, линковку — интересная. И для языков где явно можно выделить локально изменённые структурные выражения, последнее исправленное, макросы, охватывающее — такой структурный редактор даже может отрабатывать быстро. А для простых языков вроде ассемблера, алгола Action!, высокоуровневого ассемблера вроде HLA с CTFE макросами, ну или вот PL/1 (без особо навороченных макросов вроде IBM'овского PL/1, где препроцессором можно было строчку вручную сгенерировать и подать к компиляции, реализовав свой DSL, например REXX PARSE выражения через препроцессор) — сам транслятор может быть достаточно скорострельный, например, на mmap файлах в рамдиск.
HLA v3, ADK: "скорость трансляции составляет порядка несколько тысяч строк в секунду, на современном компьютере". Например, по тестам отсюда http://www.plantation-productions.com/Webster/RollYourOwn/index.html из файла BM.zip:
1) генерируем тесты bm.exe, 2) ассемблируем сгенерированный hlaBM.hla: bash$ time hla -s hlaBM.hla # to asm source only
real 0m3,517s user 0m0,015s sys 0m0,000s
bash$ time hla.exe -c hlaBM.hla #compile and assemble to object file only
real 0m7,102s user 0m0,000s sys 0m0,015s
bash$ time hla.exe hlaBM.hla #compile, assemble and link to exe
real 0m8,740s user 0m0,015s sys 0m0,000s
bash$ lines=$(wc -l hlaBM.hla) bash$ echo $lines 642967 hlaBM.hla
bash$ bc lines=624967 lines/8.740 # total to exe 71506 lines/7.102 # compile and assemble to obj file only 87998 lines/3.517 # compile to asm source only 177698 То есть даже для такого вот достаточно "высокоуровневого ассемблера" как HLA, с довольно развитым макроязыком CTFE Compile-time Function Execution, Compile-time metaprogramming language — например, объектная система и аналог STL сделаны полностью на этом макроязыке — скорость компиляции этого ассемблера, по бенчмаркам составляет порядка 180 тысяч строк в секунду (либо падает до 88..71 если заодно делать и ассемблирование и линковку). Это ещё если не использовать рамдиск, например.
Вы действительно успеете набрать 180 тысяч строк в секунду (даже "заснув на клавише Энтер" — не получится)?
Вывод: скорость компиляции для большинства нормальных языков программирования (например: HLA, ассемблер, Форт, Алгол cowgol Action!, Go или Hare, V или Vx, ну или вот PL/1, например) — ну только не С++, с бустом, шаблонами и STL, разумеется
1) достаточно быстра для большинства применений, 2) вы всё равно её не заметите, 3) большую часть времени вы всё равно проводите, тупо пялясь в монитор и думая, какую же следующую строчку написать дальше, 4) пытаясь разобраться со структурой уже существующего плохо документированного в смысле намерений кода.
Кроме, разумеется, клинических случаев вроде кода на шаблонах С++, который компилируется по полчаса, переливая из пустого в порожнее. Добавить свой отзыв
Написать автору можно на электронную почту mail(аt)compiler.su
|