Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
pred_prog_tutor.doc
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
2.03 Mб
Скачать

7.3. Склеивание переменных

Трансформация склеивания переменных есть замена в определении предиката нескольких переменных одной. Трансформация определяет список имен переменных. Переменные из указанного списка переменных заменяются первой переменной. Например, склеивание переменных a, b и c представляет систематическую замену всех вхождений имен b и c на имя a. Необходимо соблюдение условий корректности, гарантирующих эквивалентность определений предиката до и после проведения склеивания. Склеивание переменных является первой трансформацией среди перечисленных в начале разд. 7 четырех видов трансформаций. В результате склеивания уменьшается число переменных, программа становится короче и быстрее. Значительный эффект достигается при склеивании структурных переменных, таких как массивы и списки, поскольку склеивание обычно позволяет избежать копирования структур.

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

Склеивание переменных рассматривалось ранее в рамках задачи экономии памяти в классических работах А. П. Ершова, С. С. Лаврова, В. В. Мартынюка. Склеивание переменных определялось как выбор переобозначения аргументов и результатов из заданного множества корректных переобозначений, которое позволяет в наибольшей степени уменьшить объем необходимой памяти [2]. В оптимизирующей трансляции императивных программ склеивание переменных обычно реализуется на основе анализа локальных свойств программы [3; 4]. Отметим, что необходимость склеивания переменных обусловлена также тем, что в применяемых методах трансляции конструкций (особенно выражений) происходит включение в программу большого числа дополнительных промежуточных переменных.

В отличие от задачи экономии памяти [2] склеиванию подлежат только те переменные, между которыми имеется информационная связь в определении предиката. Кроме того, склеиваются лишь переменные одного типа, однако допускается различие в параметрах типа. Алгоритм автоматического оптимального склеивания переменных описан в работе [5]. В каждом операторе программы определяются аргументы и результаты оператора. Результат оператора может быть склеен с любым аргументом соответствующего типа при условии, что аргумент не используется далее после завершения исполнения оператора. В работе [5] не рассматриваются сложные формы склеивания переменных структурных типов, когда несколько структурных переменных размещаются внутри некоторой другой переменной. Пример сложной формы склеивания иллюстрируется в разд. 7.6.

7.4. Метод обобщения исходной задачи

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

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

Дадим иллюстрацию метода на примере 5.1 программы умножения через сложение. Как отмечено выше, рекурсия в программе (7.2) не является хвостовой, поскольку после рекурсивного вызова Умн(a – 1, b) в операторе c = b + Умн(a – 1, b) реализуется сложение с b. Чтобы сделать рекурсию хвостовой, необходимо внести сложение с b в рекурсивный вызов. Типичный прием  использование дополнительного параметра d в качестве накопителя при суммировании. Рассмотрим обобщенную задачу реализации умножения a * b через сложение с использованием накопителя d. Представим задачу следующим предикатом:

УмнO(nat a, b, d:  nat c) pre a ≥ 0 & b ≥ 0 & d ≥ 0 post c = a * b + d;

Тогда Умн(a, b: c)  УмнO(a, b, 0: c), и поэтому корректным является следующее определение предиката:

Умн(nat a, b:  nat c) (7.3)

pre a ≥ 0 & b ≥ 0

{ УмнO(a, b, 0: c) }

post c = a * b;

Формальное доказательство корректности (при очевидной однозначности и реализуемости) проводится доказательством истинности следующих правил, являющихся упрощенными аналогами правил LS8 и LS9.

Правило LSУ8. PУмн(a, b) ├ true (в качестве предусловия константы 0).

Правило LSУ9. PУмн(a, b) & QУмн(a, b, c)├ PУмнO(a, b, 0) & QУмнO(a, b, 0, c). □

Предикат УмнO имеет следующее определение:

УмнO(nat a, b, d:  nat c) (7.4)

pre a ≥ 0 & b ≥ 0 & d ≥ 0

{ if (a = 0) c = d else УмнO(a – 1, b, d + b: c) }

post c = a * b + d;

Доказательство корректности определения проводится так же, как и для определения Умн в примере 5.1 (см. разд. 5.5). □

Проиллюстрируем технику трансформации программы умножения, представленную определениями предикатов Умн и УмнO. На первом этапе применяется трансформация склеивания переменных. Аргумент d склеивается с результатом c, поскольку c информационно связан с d в обеих альтернативах условного оператора. Отметим, что дополнительная переменная, введенная при обобщении задачи, обычно подлежит склеиванию с некоторым результатом. Склеивание d с c будем обозначать c  d. В результате склеивания получим следующее определение:

УмнO(nat a, b, c:  nat c) { if (a = 0) c = c else УмнO(a – 1, b, с + b: c) } .

Далее применяется трансформация замены хвостовой рекурсии циклом

УмнO(nat a, b, c:  nat c) {M: if (a = 0) else |a, b, c| = |a – 1, b, с + b|; goto M } .

После раскрытия группового оператора и оформления цикла получим

УмнO(nat a, b, c:  nat c) {for (; a <> 0 ;) {a = a – 1; c = с + b} } . (7.5)

Последней является трансформация подстановки определения (7.5) на место вызова в (7.3). В соответствии с определением подстановки (7.1) получим:

|a, b, c| = | a, b, 0 |; for (; a <> 0 ;) {a = a – 1; c = с + b} .

Раскрывая групповой оператор, получим итоговую программу:

c = 0; for (; a <> 0 ;) {a = a – 1; c = с + b} .

Результатом проведенной серии трансформаций предикатной программы (7.3), (7.4) является представленная императивная программа, по качеству не хуже запрограммированной вручную. Разумеется, данная программа далеко не самая эффективная. В процессорах ЭВМ применяются более изощренные алгоритмы, использующие особенности двоичного представления чисел a и b. В предикатном программировании эффективность конечной программы в значительной степени зависит от выбора эффективного алгоритма, превращаемого в эффективную программу применением последовательности трансформаций.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]