Чтение онлайн

ЖАНРЫ

UNIX — универсальная среда программирования
Шрифт:

func ack {

 if ($1 == 0) return ($2+1)

 if($2 == 0) return (ack($1 - 1, 1))

 return (ack($1 - 1, ack($1, $2 - 1)))

}

ack(3,3)

Второй тест — стократное вычисление чисел Фибоначчи со значениями, меньшими 1000. В этом случае выполнялись в основном арифметические операции с периодическим вызовом функций:

proc fib {

 a = 0

 b = 1

 while (b < $1) {

с = b

b = a+b

a = c

 }

}

i = 1

while (i < 100) {

 fib(1000)

 i = i + 1

}

Тест

выполнялся на четырех языках:
hoc
,
bc(1)
,
bas
(древний диалект Бейсика, который существует только на PDP-11) и Си (использовался тип PDP-11 для всех переменных) .

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

time
.

Программа (3,3) 100*fib(1000)
hoc
5.5 5.0
bas
1.3 0.7
bc
39.7 14.9
c
<0.1 0.1

Таблица 8.1: Время работы на PDP-11/70 (в секундах)

Можно также приспособить Си программу для определения количества времени, используемого каждой функцией. Программу нужно перетранслировать в режиме профилирования, введя флаг

– p
в каждой единице трансляции Си и при режиме загрузки. Если изменить файл
makefile
для чтения:

hoc6: $(OBJS)

сс $(CFLAGS) $(OBJS) -lm -о hoc6

чтобы команда

сс
задействовала переменную
CFLAGS
, а затем задать

$ make clean; make CFLAGS=-p

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

mon.out
, который интерпретируется программой профилировщиком
prof
.

Для иллюстрации изложенного мы протестировали

hoc6
на приведенной выше программе Фибоначчи:

$ hoc6 <fibtest
Запуск теста

$ prof hoc6 | sed 15q
Анализ

name %time cumsec #call ms/call

_pop 15.6 0.85 32182 0.03

_push 14.3 1.63 32182 0.02

mcount 11.3 2.25

csv 10.1 2.80

cret 8.8 3.28

_assign 8.2 3.73 5050 0.09

_eval 8.2 4.18 8218 0.05

_execute 6.0 4.51 3567 0.09

_varpush 5.9 4.83 13268 0.02

_lt 2.7 4.98 1783 0.08

_constpu 2.0 5.09 497 0.22

_add 1.7 5.18 1683 0.05

_getarg 1.5 5.26 1683 0.05

_yyparse 0.6 5.30 3 11.11

$

Результаты,

полученные с помощью профилировщика, также подвержены случайным вариациям, как и те, что получены с помощью функции
time
, поэтому их следует считать лишь указанием настоящих значений, а не принимать за абсолютную истину. Тем не менее при необходимости приведенные значения могут помочь повысить быстродействие программы
hoc
. Приблизительно третья часть времени тратится на запись и чтение из стека. Накладные расходы еще более возрастут, если мы будем учитывать время выполнения функций связи
csv
и
cret
между программами Си (функция
mcount
представляет собой часть программы с профилированием, полученную с помощью команды ее
– р
.). Замена вызовов функций на макрообращения даст заметную разницу во времени выполнения.

Для проверки этого предположения мы изменили

code.c
, заменив вызовы
push
и
pop
на макрокоманды, управляющие стеком:

#define push(d) *stackp++ = (d)

#define popm *--stackp = pop /* функция все-таки нужна */

(Функция

pop
все-таки нужна в качестве кода операции нашей машины, поэтому нельзя заменить все обращения к ней.) Новая версия выполняется на 35% быстрее; время в табл. 8.1 сокращается от 5.5 до 3.7 с и от 5.0 до 3.1 с.

Упражнение 8.22

В макрокомандах

push
и
popm
не предусмотрен контроль ошибок. Прокомментируйте разумность такого решения. Как бы вы обеспечили контроль ошибок, производимый в версии с функциями, не снижая быстродействия макрокоманд?

8.8 Заключение

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

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

eqn
и
pick
, а также
yacc
,
lex
, и
make
.

Рассмотрены здесь и вопросы использования программных средств. В частности, показана роль программы

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

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

make
. Весьма важно разбить данные и подпрограммы на файлы таким образом, чтобы они не были видимы, если в этом нет необходимости.

Поделиться с друзьями: