hoc1
59
hoc2
94
hoc3
248 (для версии с
lex
229)
hoc4
396
hoc5
574
hoc6
809
Конечно, эти значения были вычислены программным способом: $
sed '/$/d' `pick *.[chyl]` | wc -l
Безусловно, развитие языка может быть продолжено, и вам предоставляется такая возможность в приведенных ниже упражнениях.
Упражнение 8.18
Измените
hoc6
так, чтобы можно было использовать поименованные формальные параметры в подпрограммах вместо
$1
и т.д.
Упражнение 8.19
Сейчас все переменные глобальны, за исключением параметров. Уже есть большая часть механизма для введения локальных переменных, хранимых в стеке. Одно из решений заключается во введении описания
auto
, которое резервирует место в стеке для перечисленных переменных; не перечисленные переменные считаются глобальными. Кроме того, придется расширить таблицу имен так, чтобы поиск в ней осуществлялся вначале для локальных, а затем для глобальных переменных. Как это связано с поименованными аргументами?
Упражнение 8.20
Как бы вы ввели массивы в язык
hoc
? Как следует передавать их функциям и процедурам? Как возвращать их?
Упражнение 8.21
Обобщите работу со строками так, чтобы переменные могли хранить строки, а не только числа. Какие операции потребуются для этого? Самая трудная часть управление памятью добейтесь динамичного хранения строк: память должна освобождаться, когда строки перестают быть нужными. В качестве промежуточного шага добавьте более развитые форматы печати, например, обеспечьте возможность использования некоторых форм стандартной Си функции
printf
.
8.7 Оценка времени выполнения
Мы сравнивали
hoc
с другими программами-калькуляторами UNIX, чтобы приблизительно оценить, насколько хорошо он работает. К таблице, представленной ниже (табл. 8.1), можно, конечно, отнестись скептически, но она показывает "разумность" нашей реализации. Все приведенные в ней величины даны в секундах. Работа велась на PDP-11/70. Было выполнено два теста. Первый, вычисление функции Аккерманна
ack(3,3)
, — хороший тест для отработки механизма вызова функций. Здесь происходят 2432 вызова, причем некоторые из них достаточно глубоко вложены.
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