Шапка

7.2 Выбор языка

Вопрос о выборе языка программирования для решения конкретной задачи не прост. Иногда решение определяется внешними факторами, подобно требованию по использованию Ada в контрактах Министерства Обороны. В других случаях выбор языка ограничивается наличием компиляторов. Но в общем случае есть достаточно вариантов для выбора языка нового проекта.

Выбор программных средств не должен рассматриваться как самостоятельный вопрос. Используемый язык должен соответствовать проектируемой системе и учитывать условия её работы. Язык должен быть пригоден для решения поставленных задач в нужное время и при заданной стоимости разработки. Он должен обеспечивать удобство последующего сопровождения и соответствовать предыдущему опыту разработчика. Отметим, что опыт программиста стоит отнюдь не на первом месте. Неправильное решение, опирающееся на стремление использовать знакомый язык, может привести к более серьёзным проблемам, чем заметное снижение скорости разработки.

7.2.1 FORTH: сила и слабость

FORTH самый естественный претендент для использования в стековом компьютере. Дело в том, что FORTH основан на наборе примитивов, предназначенных для исполнения на виртуальной стековой машине. Все описанные в книге процессоры имеют чрезвычайно эффективные реализации языка FORTH и компиляторы для него, выдающие весьма качественный машинный код. Самым большим преимуществом FORTH является наивысшая вычислительная отдача, получаемая от компьютера [* стекового компьютера, стекового!!! ] .

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

Стековая интерактивная природа FORTH приводит к ненужности написания тестовых программ. Вместо них прямо с клавиатуры передаваемые процедуре значения помещаются в стек, выполняется прогон кода и результат забирается с вершины стека. По заявлениям опытных FORTH-программистов такая интерактивная разработка модульных программ увеличивает производительность в разы, повышая качество и уменьшая стоимость проекта [* ключевое слово «опытных» ] . Увеличение производительности отчасти вызывается меньшим размером программ на FORTH, если сравнивать их с аналогами на других языках, то есть меньшим объёмом кода, который надо писать и отлаживать. Программа на языке FORTH, занимающая 32K байт программной памяти и представляющая собой исключительно рабочий словарь, считается огромной и занимает несколько сотен килобайт в исходных текстах [* больших программ на FORTH не существует ] .

Одним из преимуществ FORTH является то, что он перекрывает все уровни языков программирования. Некоторые языки, подобные ассемблеру, позволяют работать только на аппаратном уровне. Другие, как FORTRAN, абстрактны настолько, что ничего не знают о машине, на которой запущены. Программы на FORTH могут использовать все уровни абстракции. На самом нижнем FORTH позволяет прямое обращение к портам, делая возможным ввод-вывод и обработку прерываний в реальном времени, а на верхнем - может управлять сложной базой данных.

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

Расширяемость FORTH замечательна с разных точек зрения. FORTH работает как катализатор для разработчика. Хорошие программисты становятся замечательными, когда пишут на нём. Отличные становятся исключительными. Слабые выдают рабочий код, а плохие возвращаются к обычным языкам [* «Под несмолкаемый салют, крещендо "Славься.." все встают !» ] . FORTH не слишком сложен в изучении, так как он настолько отличается от всего остального, что не нуждается в отучивании от дурных привычек. Практика заставляет освоить новые способы осмысления проблем. Полученные навыки и вырабатываемый языком метод решения проблем - через модульность и дробление программ - увеличивает эффективность и при использовании других языков.

Некоторая проблема состоит в том, что в отдельных средах FORTH набор инструментов для многих программистов недостаточен. Кроме того, старые среды плохо взаимодействуют с операционными системами. Эти недостатки остались в наследство от времени, когда FORTH использовался на небольших машинах со скромными возможностями. В приложениях реального времени эти ограничения проблем не составляют. Другие направления требуют расширения набора инструментов. К счастью, каждая новая FORTH-система предлагает более удобную среду и набор библиотек, чем предшественники.

В итоге, FORTH лучше всего подходит для проектов среднего размера, требующих не более двух-трёх человек со сходным стилем программирования [* по нонешним временам - небольших проектов ] . В любых больших проектах столкновения стиля и способностей препятствуют созданию программного обеспечения очень высокого качества. С учётом изложенных ограничений [* очень, заметим, неприятных ]   программы на FORTH можно создавать за очень короткое время и с отличными результатами, часто решая задачи, которые или вовсе нельзя решить с помощью других языков, или нельзя уложить в стоимостные и временные рамки.

7.2.2 Си и другие традиционные языки

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

Чтобы проиллюстрировать возникающие вопросы, рассмотрим задачу переноса приложения, написанного на языке Си, на стековый процессор с помощью компилятора для такого процессора. Не будем заострять внимание на проблемах перевода исходных текстов в промежуточный формат, так как они не зависят от типа целевой машины. [* Здесь самое время вспомнить, что современные компиляторы уже закончили или находятся в завершающей стадии перехода с промежуточного кода для стековой виртуальной машины на код для регистровой ( последний хит LLVM )] . Интересна та часть компилятора, которая зовётся кодогенератор ( «back end» ). Кодогенератор переводит программу из промежуточного формата в исполняемый код для целевой машины.

Создание стекового кода для вычисления выражений относительно несложно. Вопрос преобразования арифметических выражений в инфиксной форме в стековую ( постфиксную или обратную польскую запись - RPN ) хорошо исследован ( Bruno & Lassagne 1975 , Couch & Hamm 1977 , Randell & Russell 1964 ).

Проблема генерации кода для языка Си состоит в том, что некоторые сведения о среде исполнения лежат в основе языка. Одним из наиболее значимых является условие существования только одного стека ( «Си-стека» ), находящегося в программной памяти и содержащего как данные, так и адреса возврата из подпрограмм. Это условие невозможно обойти, не «выведя из строя» множество уже написанных программ. Рассмотрим, например, указатель на локальную переменную. Такая переменная должна находиться в программной памяти, в противном случае код не сможет к ней обращаться.

Чтобы ещё усугубить ситуацию, Си-программы обычно кладут в Си-стек огромные объёмы данных, включающих строки и другие структуры. Затем программа прямо обращается к данным в пределах текущего стекового кадра ( части стека, содержащей переменные, которые принадлежат текущей процедуре ). Эти ограничения делают безнадёжными попытки расположить Си-стек в стеке данных целевой машины.

Как увеличить производительность стековой машины при выполнении программ, написанных на языке Си? Выход в эффективной поддержке адресации программной памяти по указателю кадра со смещением. RTX 2000 решает проблему с помощью указателя пользователя . FRISC 3 может использовать один из регистров пользователя и команды загрузки и сохранения со смещением. В планах развития RTX 32P стоит указатель кадра и отдельный сумматор для вычисления адресов. Во всех случаях доступ к локальной переменной можно выполнять за время цикла обращения к памяти - два такта: один на инструкцию и один на данные. Это самое большее, на что можно рассчитывать, для любого процессора, который не использует очень дорогие приёмы, подобные раздельным кэшам данных и инструкций.

Плохо ложится на стековые процессоры ещё одно понятие из Си и других высокоуровневых языков - «регистровые переменные». Отсутствие набора регистров предполагает отказ от возможностей оптимизации таких переменных. Это верно лишь отчасти. Стековые машины действительно не годятся для жонглирования массой промежуточных данных в стеке, но несколько значений, к которым происходят частые обращения, вполне могут там разместиться. Например, в их число могут входить счётчик циклов, хранящийся в стеке возврата и два адреса сравниваемых строк в стеке данных. В такой конфигурации можно сохранить большую часть аппаратных возможностей машины для большинства Си-программ.

Есть ещё одна идея, делающая Си-приложения в стековых машинах столь же быстрыми, как FORTH-программы. Речь идёт об использование FORTH в качестве языка ассемблера для стекового процессора. Этот подход активно продвигается несколькими производителями. В такой схеме Си-программы переносятся на стековую машину и профилируются. Данные, полученные профайлером, используются для выявления наиболее критичных циклов программы. Для увеличения скорости указанные циклы переписываются на языке FORTH с возможным добавлением специального микрокода в случае RTX 32P . Такая схема позволяет Си-приложениям достигать почти того же быстродействия, какое имеют FORTH-программы, при весьма скромных трудозатратах.

Когда к прочим достоинствами стековой машины - простоте и высокой тактовой частоте - добавляется оптимизированный код, Си становится вполне пригодным для неё языком.

7.2.3 Продукционные системы и функциональное программирование

Существует уверенность, что программные языки, используемые для построения продукционных систем, такие как Prolog, LISP и OPS-5, хорошо подходят для стековых машин. Одним очень привлекательным направлением является сочетание приложений реального времени с продукционными базами данных. Предварительные исследования этой темы обнадёживают. Много работы было выполнено на FORTH-системе в качестве отладочной платформы. Исследования проводились по следующим темам: реализация языков LISP ( Hand 1987 , Carr & Kessler 1987 ), OPS-5 ( Dress 1986 ) и Prolog ( Odette 1987 ), симуляция нейронных сетей ( Dress 1987 ) и разработка среды для экспертных систем реального времени ( Matheus 1986 , Park 1986 ). Большинство этих проектов было затем переведено на настоящие стековые машины и, кстати, с отличными результатами. Например, продукционная система Expert-5, описанная Park ( 1986 ), работала на WISC CPU/16 в 15 раз быстрее, чем на стандартном IBM PC. Аналогичная система ( на самом деле более близкая к Expert-4, и более медленная, чем Expert-5 ) на RTX 32P работала быстрее в 740 раз, чем на 4.77 MHz 8088 PC [* 16-разрядный процессор с 8-разрядной внешней шиной данных против 32/32 ] . Увеличение скорости почти на три порядка немного удивляет, но подтверждает пригодность стековых машин, которые очень эффективны при обходе деревьев и решении задач, их использующих.

Ускорение, наблюдающееся в продукционных системах, опирается на принципы, пригодные для решения широкого круга задач. Стековые машины могут рассматривать структуры данных в качестве исполняемого кода. Вообразим для примера древовидную структуру, внутренние узлы которой служат указателями, а листья - программными примитивами. Указатели - узлы деревьев - могут быть просто адресами потомков, что эквивалентно вызову подпрограмм во многих стековых процессорах. Листья могут быть исполняемыми инструкциями или вызовами процедур, выполняющих нужные действия. Обычным процессорам для обхода деревьев в поисках листьев необходим интерпретатор. Стековые же процессоры вместо этого могут выполнять дерево непосредственно. Так как вызовы подпрограмм выполняются ими очень быстро, обход будет проводиться с очень высокой скоростью. Именно техника прямого исполнения древовидной структуры «ответственна» за поразительный результат RTX 32P из предыдущего параграфа.

[* Совершенно сногсшибательное свойство, которого нет ни у одной другой архитектуры. Добавим сюда способность стекового языка к саморасширению и нечёткая грань между аппаратной и программной его реализацией ( но это конкретно про FORTH ), описанное в §7.3 . И, наконец, свойственное архитектурам MXy отсутствие склонности к рекурсивному размножению данных в длинной цепочке вызовов подпрограмм §2.1 . Уже ради только этого стоило читать книгу ].

Стековые машины хорошо подходят для программирования на языке LISP и создания экспертных систем. Дело в том, что языки LISP и FORTH очень похожи. Оба рассматривают программы, как списки функций, обращающихся к другим спискам. Оба расширяемы. Оба используют польскую запись для арифметических операций. Основным отличием является динамическое выделение памяти для объектов в LISP и статическое в FORTH. Так как нет никаких причин считать, что стековые машины уступают обычным в эффективности сборки мусора, нет оснований сомневаться в эффективности исполнения LISP на стековых машинах.

Многие аргументы в пользу LISP применимы и к Prolog. В процессе реализации Prolog для RTX 32P автор провёл исследования по эффективности перенесения языка на стековые машины. Prolog использует типизированные данные, которые могут быть как собственно данными, так и ссылкой на них. Одним их возможных способов кодирования элементов в Prolog может быть признак типа, занимающий старшие девять бит 32-разрядного слова. Младшие 23 бита при этом могут служить указателем на другой узел, указателем на 32-битную константу или короткой константой. В таком формате данные можно исполнять, как команды. Инструкции для RTX 32P можно составить так, чтобы позволить обход достаточно длинной серии разыменовывания ссылок со скоростью одно разыменовывание за один цикл памяти, просто исполняя структуру, как программу. Проверка «пустых» указателей может быть выполнена объявлением значения «Nil» указателем на программу-обработчик ошибки. Такой способ обращения с данными просто невозможен в других архитектурах.

Функциональные языки обещают новый способ решения проблем с использованием иной, нежели в традиционных компьютерах, вычислительной модели ( Backus 1978 ). Характерной особенностью исполнения функциональных программ является сокращение графа. Те же приёмы прямого исполнения графа программы, что и в обсуждении продукционных систем, применимы и к его сокращению. Таким образом, стековые машины должны хорошо подходить для исполнения программ на функциональных языках. Belinfante ( 1987 ) опубликовал метод сокращения графа выполненный на FORTH. Koopman & Lee ( 1989 ) описали систему сокращения графа в виде потокового интерпретатора.

С теоретической точки зрения машины, хорошо сокращающие графы, например, G-machine или NORMA , попадают в категорию SL0 по классификации из Части _2   книги. Группа ML0 включает все возможности SL0 машин и, таким образом, тоже может эффективно сокращать графы. Начальные исследования этого вопроса автором показали, что RTX 32P , являющийся очень простой стековой машиной, может быть сравним по эффективности со столь сложными компьютерами, как NORMA .

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

Previous part:

Next part: