Научная статья на тему 'Компилятор в системе функционального программирования SFP'

Компилятор в системе функционального программирования SFP Текст научной статьи по специальности «Компьютерные и информационные науки»

CC BY
281
43
i Надоели баннеры? Вы всегда можете отключить рекламу.
Ключевые слова
ФУНКЦИОНАЛЬНОЕ ПРОГРАММИРОВАНИЕ / ПОТОКОВЫЕ ЯЗЫКИ / ТРАНСЛЯЦИЯ / ОПТИМИЗАЦИЯ / РАСПАРАЛЛЕЛИВАНИЕ / FUNCTIONAL PROGRAMMING / DATAFLOW LANGUAGES / TRANSLATION / OPTIMIZATION / PARALLELIZATION

Аннотация научной статьи по компьютерным и информационным наукам, автор научной работы — Стасенко Александр Павлович, Пыжов Константин Анатольевич, Идрисов Ренат Искандерович

Статья посвящена описанию компилятора в системе функционального программирования SFP (System of Functional Programming). Вкратце описываются основные черты функционального языка Sisal 3.2, являющего входным языком системы SFP. Рассмотрено первое внутреннее представление IR1 и схема трансляции входного языка в него. Описаны дальнейшие внутренние представления IR2 и IR3 и фазы трансляции между ними и, в частности, фазами оптимизации и распараллеливания. Представлены результаты исследования эффективности оптимизирующих преобразований и результаты исполнения программ на SMP архитектуре.

i Надоели баннеры? Вы всегда можете отключить рекламу.
iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.
i Надоели баннеры? Вы всегда можете отключить рекламу.

Compiler of system of functional programming (SFP)

Paper describes compiler of system of functional programming (SFP). The main features of SFP input language Sisal 3.2 are briefly introduced. The structure of first internal representation (IR1) and translation scheme from an input language to it is shown. Paper presents the following internal representations IR2 and IR3 as well as translation phases between them and phases of optimization and parallelization in particular. The benchmark results of program optimizing transformations and execution on SMP architecture are shown.

Текст научной работы на тему «Компилятор в системе функционального программирования SFP»

УДК 519.68 + 681.3.06

Институт систем информатики им. А. П. Ершова СО РАН пр. Академика Лаврентьева, 6, Новосибирск, 630090, Россия Новосибирский государственный университет ул. Пирогова, 2, Новосибирск, 630090, Россия E-mail: 1 astasenko@gmail.com, 2 pyjov@ngs.ru, 3 ren@ngs.ru

КОМПИЛЯТОР

В СИСТЕМЕ ФУНКЦИОНАЛЬНОГО ПРОГРАММИРОВАНИЯ SFP *

Статья посвящена описанию компилятора в системе функционального программирования SFP (System of Functional Programming). Вкратце описываются основные черты функционального языка Sisal 3.2, являющего входным языком системы SFP. Рассмотрено первое внутреннее представление IR1 и схема трансляции входного языка в него. Описаны дальнейшие внутренние представления IR2 и IR3 и фазы трансляции между ними и, в частности, фазами оптимизации и распараллеливания. Представлены результаты исследования эффективности оптимизирующих преобразований и результаты исполнения программ на SMP архитектуре.

Ключевые слова: функциональное программирование, потоковые языки, трансляция, оптимизация, распараллеливание

Введение

В настоящее время рост производительности вычислительных систем происходит не за счет увеличения быстродействия процессоров, а благодаря массовому распространению многопроцессорных и многоядерных систем, что, в свою очередь, приводит к возобновлению интереса к параллельным вычислениям. Однако существующие подходы описания параллелизма в широко распространенных системах и языках программирования имеют ряд недостатков. Пользователь вынужден либо описывать параллелизм в своей программе явно, либо довольствоваться результатами автоматического выделения параллельных фрагментов в программе. Первый подход является недостаточно удобным и универсальным и приводит к появлению трудно обнаружимых ошибок, в то время как второй не обеспечивает достаточно эффективного выделения параллелизма, как правило, вследствие ограничений, накладываемых семантикой языков или средствами описания вычислительных задач. Кроме того, автоматическое распараллеливание требует анализа зависимостей по данным, который в общем случае является весьма сложным [Ахо и др., 2001; Allen, Kennedy, 2002; Muchnik, 1997; Касьянов, Поттосин, 1986].

Одним из решений описанных проблем является применение потоковых языков программирования, которые в своем большинстве являются функциональными языками с типами и операциями, ориентированными на параллельное исполнение [Johnston et al., 2004]. Функциональные языки задают непосредственно сами вычисления в виде применений функций к их аргументам, а не последовательность действий или операций, реализующую эти вычисления. Такое неявное описание позволяет автоматизировать планирование порядка вычислений на этапе подготовки программы к исполнению, причем с учетом архитектуры конкретной вычислительной системы. Кроме того, в случае с потоковыми языками отпадает необходимость в сложном и трудоемком анализе зависимостей по данным, поскольку единственный вид зависимостей - потоковые, или истинные зависимости, представлены в программе в явном виде. Таким образом, функциональная программа задает машинно-независимый низкоуровневый параллелизм, однако компилятору функционального языка программирования для генерации эффективного кода для целевой машины (особенно архитектуры фон Неймана) требуется выполнить намного больше действий, чем компилятору императивного языка программирования [Cann, Evripidou, 1995; Gharachorloo et al., 1988; Cann, 1989].

* Работа выполнена при частичной финансовой поддержке Российского фонда фундаментальных исследований (грант № 07-07-12050).

ISSN 1818-7900. Вестник НГУ. Серия: Информационные технологии. 2008. Том 6, выпуск 3 © А. П. Стасенко, К. А. Пыжов, Р. И. Идрисов, 2008

Статья посвящена описанию компилятора в системе функционального программирования SFP [Kasyanov et al., 2006]. Система SFP ставит целью предоставить прикладному программисту на его рабочем месте удобную среду для разработки функциональных программ, предназначенных для последующего исполнения, возможно, на удаленных высокопроизводительных параллельных вычислительных системах. Входным языком системы SFP в настоящий момент является язык Sisal 3.2 [Стасенко, 2007а]. Для версии языка Sisal 3.1 [Стасенко, Синяков, 2006] имеется работающий прототип компилятора (полностью

поддерживающий входной язык), генерирующий на выходе текст C# [ISO, 2003] программы с использованием стандартной библиотеки для порождения нитей (threads).

Общая схема работы компилятора Sisal 3.1 представлена на рис. 1. Исходная Sisal программа («Source» на схеме) поступает на вход транслятора переднего плана (front-end транслятора), который транслирует ее в первое внутреннее представление IR1. Далее граф IR1 подается на вход back-end части компилятора. Back-end часть включает следующие фазы (где фазы оптимизации являются необязательными): трансляция из IR1 в IR2 («IR2 Gen» на схеме), оптимизация IR2 (IR2 Opt), трансляция из IR2 в IR3 с генерацией параллельного кода (IR3 Gen), оптимизация IR3 и понижение уровня IR3 (IR3 Opt) и трансляция IR3 в код целевой архитектуры (CodeGen).

Рис. 1. Схема компиляции и исполнения Sisal программ

Целевой платформой для компилятора Sisal 3.1 является платформа .NET. Планируется разработка кодогенератора, транслирующего представление IR3 в код MSIL, являющегося языком ассемблера виртуальной машины платформы .NET. В настоящий момент в компиляторе Sisal 3.1 в качестве кодогенератора используется транслятор внутреннего представления IR3 в программу на языке C#, которая далее транслируется в код MSIL компилятором языка C#. Разработана библиотека, содержащая систему классов C#, обеспечивающих поддержку периода исполнения для Sisal программ. Кроме того, пользователь может использовать эти классы для обеспечения взаимодействия Sisal-программы и кода на языке C# (например, организовывать ввод-вывод).

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

GIPC, являющийся частью системы GIPSY (General Intensional Programming System) [Paquet et al., 2004], предназначается для перевода программ с различных диалектов функционального потокового языка Lucid, набор операций которого свойственен парадигме содержательного (intensional) программирования [Ashcroft, Faustini, 1995]. Язык Lucid включает поддержку задания крупнозернистого параллелизма с описанием последовательных частей на языке Си или другом императивном языке программирования. Компилятор GIPC порождает совокупность промежуточных представлений: представление IDS для описания потоковых зависимостей, представление CST для описания последовательного кода и ICP для описания процедур взаимодействия представлений IDS и CST.

Компилятор языка SAC (Single Assignment C) [Sholz, 2003], генерирующий на выходе программу на языке Си, является подмножеством языка Си, допускающим легкое преобразование в функциональную программу, которое расширено поддержкой многомерных массивов, возможно, с неизвестной во время компиляции размерностью. Компилятор языка SAC поддерживает высокоуровневые оптимизирующие преобразования над массивами и циклами и работает с промежуточным представлением, основанным на SSA-форме (Static Single Assignment) [Muchnik, 1997].

Для языка Sisal версии 1.2 [McGrow et al., 1985] существует оптимизирующий компилятор промышленного уровня OSC [Cann, 1992], генерирующий на выходе высокоэффективный код на языках Си и Фортран для последовательных, векторных и параллельных машин с общей памятью. В системе SFP используется внутреннее представление IR1 [Стасенко, 2004], основывающееся на графах потока данных промежуточного представления IF1 [Skedzielewski, Glauert, 1985], использующегося в компиляторе OSC. Из рассмотренных промежуточных представлений, представление IF1 наиболее удобно для оптимизирующих преобразований, так как позволяет явным образом задавать сложные конструкции языка, включая циклические конструкции.

Язык Sisal 3.2

Язык Sisal (Streams and Iterations in a Single Assignment Language) ориентирован на поддержку научных вычислений, способствует разработке корректных детерминированных параллельных программ и освобождает от многих сложностей параллельного программирования, позволяя концентрироваться на конструкции алгоритмов. Например, следующая функция на языке Sisal 3.1 осуществляет быструю сортировку целочисленного массива: function sort(A: array[integer] returns array[integer])

if size(A) <= 1 then A // случай, когда ничесго упорядочивать не нужно

else let /* иначе возращаем результат выражения «let», которое сопоставляет именам значения массивов чисел меньших, равных и больших числа A[1] (числа берутся из массива A)

*/

Less := for a in A returns array of a when a < A[1] end for;

Same := for a in A returns array of a when a = A[1] end for;

More := for a in A returns array of a when a > A[1] end for

// результат выражения «let» - конкатенация (\\) трех массивов

in sort(Less) || Same || sort(More) end let end if end function

Язык Sisal 3.2 является существенно переработанной версией языка Sisal 3.1, основные отличия от которого следующие. Во-первых, после исследования возможностей языка Sisal 2.0 [Bohm et al., 1991] было принято решение обогатить язык Sisal 3.1 поддержкой многомерных массивов и значительно расширенными возможностями по их генерации.

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

Операция перемножения матриц задается с помощью обобщенной процедуры. Каждой обобщенной процедуре сопоставляется ранее определенный контракт, в котором указаны, какие операции должны поддерживать типы, задаваемые параметрами обобщенной процедуры (например, для типа элементов матрицы это операции сложения и умножения). Далее приведен пример описания типа матрицы и операции их перемножения: contract additive[T]

operation + (T, T returns T) operation * (T, T returns T)

end contract

type matrix[T] = array [..,..] of T

operation * of additive[T] (matrix[T], matrix[T] returns matrix[T])

В-третьих, в языке Sisal 3.2 расширены возможности взаимодействия с другими языками программирования, для чего в язык были введены элементы мультипрограммирования в виде концепции инородных типов. Инородные типы задаются некоторым строковым представлением на другом языке программирования. Значения инородных типов конструируются с помощью инородных операций и функций (написанных на другом языке программирования и расположенных в другом модуле со специальным интерфейсом на языке Sisal 3.2). С помощью инородных типов можно задавать архитектурно-зависимые типы и определять операции неявного преобразования между ними и «машинно-независимыми» типами языка Sisal. Например, можно определить инородные типы целых и вещественных чисел фиксированного размера и определить операции неявного преобразования между ними и типами «integer» и «real» языка Sisal. Также на инородных типах основывается возможность использования языком Sisal 3.2 функций уже написанных программ на других языках программирования.

Внутреннее представление IR1

Внутреннее представление IR1 [Стасенко, 2004] состоит из множества ориентированных ациклических графов IR1, соответствующих функциям исходной Sisal-программы. Граф IR1 задается тройкой G = (N, P, E, Pm, P°u), где N - множество вершин; P - множество портов; E с PxP - множество дуг; Pin с P - множество входных портов графа G; Pout с P - множество выходных портов графа G. Каждой вершине Ni е N соответствуют два подмножества портов из P: входные порты P,m с P и выходные порты P°ut с P. Дуга Ei = (P1, P2) е E, если для некоторых i и jP1 е P°ut и P2 е P/n u Pout или P1 е Pin и P2 е P/n u Pout.

Вершины графа IR1 задают операции, порты задают аргументы и результаты операций, а дуги задают информационные зависимости между ними. Тем самым граф IR1 задает поток вычислений. Граф IR1 является иерархическим графом [Касьянов, 1999], так как множество вершин N включает вершины двух видов: простые и составные. Составные вершины содержат дочерние графы IR1, зависимости между портами которых выражаются неявным образом, и обозначают структурированные конструкции языка Sisal - такие, как условные выражения и циклы. Каждой дуге графа IR1 приписан тип пересылаемого ею значения, если IR1 задает строго типизированный язык. На рис. 2 изображены 1 графы IR1, задающие функцию «sort», рассмотренную ранее.

Во внутреннем представлении IR1 условные выражения задаются составной вершиной Select, первый граф которой является управляющим. Управляющий граф имеет один выход целого типа, значение которого определяет то, какой из графов составной вершины Select (начиная со второго графа) будет использоваться для генерации ее выходных значений. На рис. 2, б изображено содержимое составной вершины Select с рис. 2, а. На рис. 2, в, г, д изображено содержимое первого, второго и третьего графов составной вершины Select соответственно. Литералы в графе IR1 задаются вершинами без входных портов и с одним выходным портом. Так как внутреннее представление IR1 описывается иерархией ациклических графов, то вызовы функций на рис. 2, д задаются литералом функционального типа с именем функции, однозначно указывающим ее граф. 1

1 Рисунки IR1-графов сгенерированы автоматически по внутреннему представлению IR1, построенному транслятором переднего плана.

□ о □

ж

Рис. 2. Графы внутреннего представления функции «sort»: а - граф функции «sort»; б - граф составной вершины Select; в - первый граф вершины Select; г - второй граф вершины Select; д - третий граф вершины Select; е - граф составной вершины Forall; ж - второй граф вершины Forall; з - четвертый граф вершины Forall

е

Во внутреннем представлении IR1 циклические выражения задаются составными вершинами LoopA. LoopB и Forall. которые задают циклы do-while, while и for соответственно. Данные составные вершины имеют постоянное число графов. описывающих управление циклом. тело цикла и его предложение возврата. На рис. 2. е изображено содержимое составной вершины Forall с рис. 2. д (соответствующей массиву «Same»). На рис. 2. ж. з изображено содержимое второго (задающего генерацию диапазона) и четвертого (задающего предло-

жение возврата) графов составной вершины Forall соответственно (первый и третий графы являются пустыми).

Схема front-end транслятора языка Sisal 3.1

Для разработки front-end транслятора языка Sisal, была создана модель у-автомата [Стасенко, 20076], позволяющая наглядно в терминах у-схем описывать синтаксис языка в непосредственно исполняемом виде. На рис. 3 представлен пример у-схемы, задающей у-автомат для разбора скобочного языка, описываемого грамматикой G = (T, N, S, R), где T = { ‘(’, ‘)’ }, N = { S, A } и R = { «S ^ A», «A ^ S», «A ^ S А» }. Модель у-автомата основана на классической модели магазинного автомата [Касьянов, Поттосин, 1986], функция переходов которого ограничена одним из трех видов:

• переходы, не зависящие от содержимого магазина и не меняющие его содержимое (сплошные дуги с пометками из множества T и штриховые дуги s-переходов на рис. 3);

• переходы, не зависящие от содержимого магазина, но добавляющие к нему состояния (жирные пунктирные дуги на рис. 3);

• переходы в состояние, вытолкнутое из магазина (дуги в вершину «pop» на рис. 3).

^7

* S2 "

)

Рис. 3. у-схема у-автомата для разбора скобочного языка

Класс языков детерминированных у-автоматов совпадает с классом ZZi-языков [Касьянов, Поттосин, 1986]. Разбор более широких классов языков описывается неявно с помощью контекстных состояний. Кроме того, модель у-автоматов включает средства иерархической обработки неопределенностей для обработки ошибок трансляции без накладных расходов полного определения у-автомата.

С помощью у-схем можно реализовать транслятор, осуществляющий лексический, синтаксический и семантические разборы языков программирования. Для этого к вершинам и дугам у-схем добавляются пометки, состоящие из идентификаторов транслирующих процедур. Реализация процедур в формализме у-схем не уточняется и может иметь общий модифицируемый контекст исполнения, позволяющий задавать трансляцию произвольной сложности. Транслятор разделяется на графическую, текстовую и интерпретирующую части. Графическая часть описывает у-схему, задающую у-автомат, который распознает синтаксис языка. Текстовая часть содержит описание контекстно-зависимых переходов («семантики отношений») и транслирующих процедур («операционной семантики»), использующихся в у-автомате. Интерпретирующая часть транслятора исполняет его графическую и текстовую части. Общая схема транслятора приведена на рис. 4 и отражена в разработанной системе интерфейсов взаимодействия с транслятором [Стасенко, 2005].

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

Front-end транслятор языка Sisal 3.1 системы SFP спроектирован с применением у-схем и обеспечивает простоту учета модификаций языка, приемлемую скорость трансляции, качественные сообщения об ошибках и предупреждениях и развитые механизмы восстановления после ошибок разбора. Использование у-схем позволило сократить объем текста front-end транслятора языка Sisal 3.1 до десяти тысяч строк, по сравнению с тридцатью тысячами строк кода аналогичной части транслятора OSC 12.0 для более простой версии языка Sisal 1.2 [McGraw et al., 1985].

текст программы языка программирования

лексический анализ

у-автомат для лексического анализа

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

код процедур, генерирующих лексическую свёртку

Рис. 4. Схема front-end транслятора, реализованного с применением у-схем

Внутреннее представление IR2

Внутреннее представление IR2 основывается на языке IF2 [Welcome et al., 1986]. Граф IR2 для функции языка Sisal - это объект (G, VAR, аи, <р), где G = (N, P, E, Pm, Pout) - граф IR1, определенный выше; VAR - множество переменных; си - отображение E ^ VAR, задающее привязку переменных к дугам графа G; <р - порядок на N х N, задающий последовательность исполнения [Vijayaraghavan et al., 1991]. Все вершины графа IR2 упорядочены отношением <р, которое строится исходя из потока данных. А именно, если существует путь из вершины N1 в N2, причем N1 и N2 имеют один уровень вложенности, полагается N1 <р N2. Для вершин, которые содержатся в составных вершинах условных выражений и циклов, отношение последовательности исполнения определяется семантикой составной вершины. Если N1 и N2 не связаны путем и нет ограничений, накладываемых правилами вычисления составной вершины, полагаем N1 =р N2 2.

Представление IR2 строится для транслируемого модуля и является совокупностью графов IR2 для функций, содержащихся в данном модуле. На рис. 5 показано представление IR2 функции языка Sisal, вычисляющей знак числа:

function sign(N: integer returns integer)

if N > 0 then 1 elseif N < 0 then -1 else 0 end if

end function

Для внутренних представлений IR2 (и IR3) определяются объекты переменная и тип. Тип в IR2 (и IR3) служит для представления типов языка Sisal на уровне back-end транслятора. Тип содержит низкоуровневую информацию об объекте, с которым ассоциирован этот тип (такую, как машинное представление типа и класс памяти).

Переменная описывает объекты языка Sisal на уровне IR2 и IR3. В представлении IR2 переменные ассоциируются с дугами графа (в IR3 - служат операндами операций IR3). Каждая переменная имеет следующие свойства: уникальный идентификатор, уникальное имя, тип и дополнительная булева переменная, отвечающая за свойство «is error». Переменные делятся на скалярные, массивные и записи. Каждая группа переменных имеет некоторые дополнительные свойства. Скалярные переменные дополнительно имеют размер в байтах. Переменные-массивы дополнительно содержат три вспомогательные переменные: переменная, опи-

2 Отношение <р используется для фиксации последовательности, в которой должны исполняться операторы, полученные для вершин IR2. Если N1 <р N2, то вычисление N1 должно обязательно происходить перед вычислением N2. Если же оказалось, что N1 =р N2, выполнять операции для этих вершин можно в любом порядке; кроме того, возможно их параллельное исполнение.

сывающая элемент массива, нижняя граница массива и его размер. Переменные-записи содержат список переменных, описывающих поля данной записи.

Рис. 5. Граф IR2 функции «sign»

С точки зрения структуры граф IR2, полученный сразу после трансляции из IR1 в IR2, является изоморфным графу IR1, поэтому собственно создание вершин и дуг представления IR2 является тривиальным. После создания графа выполняется построение отображения си, т. е. аннотирование дуг графа IR2 переменными. Дугам, выходящим из одного порта, приписывается одна и та же переменная. Дуги, выходящие из разных портов, имеют различные переменные. Этим достигается выполнение требования единственности определения каждой переменной. В дальнейшем (при оптимизации IR2) распределение переменных по дугам графа может меняться. Заключительным этапом построения IR2 является упорядочивание вершин отношением приоритета исполнения <р.

Фаза оптимизации IR2 выполняет оптимизацию распределения переменных агрегатных типов для дуг графа и вынос инвариантных вычислений из циклов. Оптимизация переменных агрегатных типов заключается в перераспределении переменных по дугам графа, которое позволяет избежать избыточного копирования объектов. Это преобразование выполняется в два прохода: первый проход - поиск вершин, которые являются единственными использованиями агрегатных типов; второй - привязывание ко входу и выходу каждой из таких вершин одной и той же переменной. Рассмотрим функцию «tr» на языке Sisal:

function tr(A: array[array[integer]] returns array[array[integer]]) for i in 1, size(A) cross j in i, size(A) repeat A := old A[i, j := A[j, i]] returns value of A end for end function

Граф IR2 для функции «tr» внутри двух вложенных циклов содержит вершину AReplace, которая порождает новый массив с одним измененным элементом. После построения графа IR2 эта вершина имеет уникальные переменные на своих входных и выходных портах. Это значит, что на каждой итерации цикла будет создаваться новая копия массива A. Однако после оптимизации вход и выход вершины AReplace, соответствующие массиву, будут нести одну и ту же переменную (поскольку вершина AReplace является единственным пользователем этого входа).

Вынос инвариантных вычислений является преобразованием графа IR2, которое направлено на удаление из тела цикла всех вычислений, которые могут быть выполнены за преде-

лами цикла. Рассмотрим функцию «foo» на языке Sisal (на рис. 6 показано представление IR2 до и после выноса инвариантных вычислений из цикла):

function foo(A: array[integer]; k: integer; N: integer returns integer)

let s := 0 in for i in 1,100 repeat s := old s + k * A[N] returns value of s end for end let end function

Рис. 6. IR2 функции «foo» до (а) и после (б) выноса инвариантных вычислений

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

Внутреннее представление IR3

Представление IR3 является среднеуровневым императивным представлением программы, состоящим из операторов и выражений. Ниже приведена распечатка представления IR3 для функции, возвращающей знак целого числа (после фазы оптимизации IR3):

0 entry "function sign[integer]" (V 1(I32) returns V 3(I32)); {

5 V_7(BOOL) = (0x0(I32) < V_1(I32));

6 V_11(BOOL) = (V_1(I32) < 0x0(I32));

7 if (V_7(BOOL) == true(BOOL)) {

11 V_3(I32) = 0x1(I32);

} else {

12 if (V_11(BOOL) == true(BOOL)) {

17 V_3(I32) = - 0x1(I32);

} else {

19 V_3(I32) = 0x0(I32);

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

}

}

20 return;

}

В процессе трансляции из IR2 в IR3 для графа потока данных вычислений IR2 фактически строится императивная программа (последовательность операторов), выполняющая вычис-

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

Блок оптимизации внутреннего представления IR3 выполняет такие преобразования, как распространение копий, удаление мертвых присваиваний и удаление бесполезных присваиваний [Ахо и др., 2001]. Эффективность оптимизации на примере задачи умножения двух матриц показана на рис. 7, а. Пунктирной линией обозначен рост времени исполнения полученного кода в зависимости от размерности входных матриц без каких-либо оптимизирующих преобразований; сплошной линией - при применении всех реализованных оптимизаций на уровнях IR2 и IR3.

После выполнения оптимизирующих преобразований выполняется «понижение уровня» IR3, которое заключается в замене «фиктивных вызовов функций» (фиктивные функции, или intrinsic functions, - это множество внутренних функций компилятора, вызовы которых всегда заменяются на некоторый фрагмент внутреннего представления) на последовательность простых операторов или фиктивных функций более низкого уровня, которые заменяются уже на фазе кодогенерации.

Распараллеливание на уровне IR3 заключается в создании вспомогательных структур, относящихся непосредственно к целевому языку трансляции. На этом шаге создаются дополнительные переменные и классы, которые требуются для описания параллельной работы программы. Эффективность распараллеливания на примере задачи умножения двух квадратных матриц с размерностью 400 представлена на рис. 8, где показана зависимость времени исполнения от количества параллельных потоков. Тесты были проведены на 4-процессорной ЭВМ с SMP-архитектурой.

0 12345678

б

N

Рис. 7. Эффективность оптимизации (а) и распараллеливания (б)

Заключение

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

Список литературы

Ахо А., Сети Р., Ульман Дж. Компиляторы. Принципы, технологии, инструменты. М.: Вильямс, 2001. 768 с.

Касьянов В. Н. Иерархические графы и графовые модели: вопросы визуальной обработки // Проблемы систем информатики и программирования. Новосибирск, 1999. С. 7-32.

Касьянов В. Н., Поттосин И. В. Методы построения трансляторов. Новосибирск: Наука, 1986. 330 с.

Стасенко А. П. Система интерфейсов транслятора во внутреннее представление IR1 // Методы и инструменты конструирования и оптимизации программ. Новосибирск, 2005. С.229-238.

Стасенко А. П. Автоматная модель визуального описания синтаксического разбора // Методы и инструменты конструирования программ. Новосибирск, 2007б. С. 186-209.

Стасенко А. П. Внутреннее представление системы функционального программирования Sisal 3.0. Новосибирск, 2004. 54 с. (Препр. / РАН. Сиб. отд-ние. ИСИ; № 110)

Стасенко А. П. Язык программирования Sisal 3.2 // Методы и инструменты конструирования программ. Новосибирск, 2007а. С. 56-134.

Стасенко А. П., Синяков А. И. Базовые средства языка Sisal 3.1. Новосибирск, 2006. 60 с. (Препр. / РАН. Сиб. отд-ние. ИСИ; № 132)

Allen R., Kennedy K. Optimizing Compilers for Modem Architectures. San Francisco: Morgan Kaufmann, 2002. 816 p.

Ashcroft E. A., Faustini A. A. Multidimensional Programming. L.: Oxford University Press, 1995. 176p.

Bohm A. P. W., Cann D. C., Feo J. T., Oldehoeft R. R. The Sisal 2.0 Reference Manual Livermore, CA, 1991. 128 p. (Tech. Rep. / Lawrence Livermore National Laboratory; UCRL-MA-109098)

Cann D. C. The Optimizing Sisal Compiler. Livermore, CA, 1992. 74 p. (Tech. Rep. / Lawrence Livermore National Laboratory; UCRL-MA-110080)

Cann D. C. Compilation Techniques for High Performance Applicative Computation: PhD thesis. Fort Collins: Colorado State University, 1989. 145 p.

Cann D. C., Evripidou P. Advanced Array Optimizations for High Performance Functional Languages // IEEE Transactions on Parallel and Distributed Systems. N. Y.: IEEE Computer Society Press, 1995. Vol. 6. No. 3. P. 229-239.

Gharachorloo K., Sarkar V., Hennessy J. L. A Simple and Efficient Implementation Approach for Single Assignment Languages // Proceedings of the ACM conference on LISP and functional programming. N. Y.: ACM Press, 1988. P. 259-268.

ISO/IEC 23270:2003(E). Information technology: C# language specification. Geneva: Internat. Organization for Standardization (ISO), Central Secretariat, 2003.

Johnston W. M., Paul Hanna J. R., Millar R. J. Advances in Dataflow Programming Languages // ACM Computing Surveys. N. Y.: ACM Press, 2004. Vol. 36. No. 1. P. 1-34.

Kasyanov V. N., Stasenko A. P., Gluhankov M. P., Dortman P. A., Pyjov K. A., Sinyakov A. I. SFP - An Interactive Visual Environment for Supporting of Functional Programming and Supercomputing // WSEAS Transactions on Computers. 2006. Vol. 5. No. 9. P. 2063-2070.

McGraw J. R., Skedzielewski S. K., Allan S. J., Oldehoeft R. R., Glauert J., Kirkham C., Noyce B., Thomas R. Sisal: Streams and Iterations in a Single Assignment Language, Language Reference Manual, Version 1.2. Livermore, CA, 1985. (Tech. Rep. / Lawrence Livermore National Laboratory; M-146, Rev. 1)

Muchnik S. S. Compiler Design and Implementation. San Francisco: Morgan Kaufmann, 1997. 856 p.

Paquet J., Wu A., Grogono P. Towards a Framework for the General Intensional Programming Compiler in the GIPSY // Proceedings of the conference on Object Oriented Programming Systems Languages and Applications. N. Y.: ACM Press, 2004. P. 164-165.

Scholz S.-B. Single Assignment C - Efficient Support for High-Level Array Operations in a Functional Setting // Journal of Functional Programming. 2003. Vol. 13. No. 6. P. 1005-1059.

Skedzielewski S. K., Glauert J. IF1 - An Intermediate Form for Applicative Languages, Version 1.0. Livermore, CA, 1985. 68 p. (Tech. Rep. / Lawrence Livermore National Laboratory; M-170) Vijayaraghavan V., Kavi K. M., Shirazi B. Control Flow Extensions to the Dataflow Language Sisal // Proceedings of the Symposium on Applied Computing. N. Y.: IEEE Computer Society Press, 1991. P. 130-138.

Welcome M. L., Skedzielewski S. K., Yates R. K., Ranelletti J. E. IF2 - An Applicative Language Intermediate Form with Explicit Memory Management. Livermore, CA, 1986. 37 p. (Tech. Rep. / Lawrence Livermore National Laboratory; M-195)

Материал поступил в редколлегию 26.08.2008

A. P. Stasenko, K. A. Pyjov, R. I. Idrisov

Compiler of System of Functional Programming (SFP)

Paper describes compiler of system of functional programming (SFP). The main features of SFP input language Sisal 3.2 are briefly introduced. The structure of first internal representation (IR1) and translation scheme from an input language to it is shown. Paper presents the following internal representations IR2 and IR3 as well as translation phases between them and phases of optimization and parallelization in particular. The benchmark results of program optimizing transformations and execution on SMP architecture are shown.

Keywords: functional programming, dataflow languages, translation, optimization, parallelization.

i Надоели баннеры? Вы всегда можете отключить рекламу.