Google:
Помощь

Описание языка Т++

1. Общие сведения

Язык T++ является входным языком Т-системы — системы параллельного программирования c открытой архитектурой, поддерживающей автоматическое динамическое распараллеливание программ.Синтаксически язык T++ максимально приближен к языку C++.

Явные параллельные конструкции, понимаемые в привычном смысле, в T++ отсутствуют (т.е. программист не указывает, какие части функций следует выполнять параллельно), и реально счетные гранулы выделяются динамически во время работы Т-программы. Указаниями для такого выделения являются расширения T++ синтаксиса и семантики языка C++. Эти расширения представляют собой несколько ключевых слов и выглядят достаточно прозрачными для синтаксиса и семантики языка C++: программу на языке T++ можно разрабатывать и отлаживать в однопроцессорном режиме, без использования Т-системы. Для этого достаточно переопределить нужным образом с помощью макроопределений эти ключевые слова. Данное свойство упрощает первый этап цикла разработки программ, позволяя отлаживать их в наиболее удобной, привычной для программиста последовательной среде.

В языке T++ поддерживается функциональный подход к написанию программ: все данные Т-функция («чистая» функция в языке Т++) может получать только через входные аргументы, а результаты своей работы возвращать только через выходные аргументы. Т-функция не должна иметь побочных эффектов. Таким образом, Т-функция естественным образом служит гранулой параллелизма: если она вызывается, она через свои аргументы получает всю информацию, необходимую ей для работы, и поэтому ее можно "отдать на выполнение" другому процессору.

2. Основные элементы языка

Набор ключевых слов языка T++ невелик, и освоить его не составит труда для любого программиста, привыкшего к написанию программ на языке C.

Основные ключевые слова языка T++:

  1. tval — атрибут, указываемый в определениях Т-переменных, которые могут содержать "неготовые" значения.

  2. tptr — употребляется для описания "удаленного" указателя — указателя на Т-объект (например, неготовое значение), который может находиться на другом узле кластера.

    Удаленный указатель содержит в себе не только информацию об адресе в памяти, но и номер вычислительного узла кластера, в памяти которого находится Т-объект. Употреблять ключевое слово tptr в программе можно в декларациях переменных так же, как в языке С употребляется оператор '*' в значении "указатель". tptr всегда указывает на переменную, которая может иметь неготовое значение (такие переменные объявляются с атрибутом tval).

    Ограничение реализации: нельзя указывать атрибут tptr у объектов, которые могут иметь конструкторы.

  3. tout — атрибут, указываемый в определениях Т-функций у выходных результатов.

    Употреблять ключевое слово tout в программе можно в декларациях переменных так же, как в языке С употребляется оператор '&' в значении "ссылка". Ключевое слово tout может встречаться только в описании аргументов Т-функции. Например:

    #include <stdio.h>
    
    tfun int f (tout int a) {
      a = 10;
      return 0;
    }
    tfun int main (int argc, char *argv[]) {
      tval int v;
      f(v);
      printf("v=%d\n",(int)v);
      return 0;
    }

    Программа напечатает "v=10".

  4. tfun — признак того, что определяемая функция может вычисляться параллельно (Т-функция).

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

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

            tfun int abcd(tval int a, double tptr b, tout int c, tout double d);

    Вызов Т-функции синтаксически не отличается от вызова С-функции. Но иногда может понадобится явное приведение типа возвращаемого результата работы функции для немедленного вычисления этой Т-функции и получения результата.

    Программа на языке T++, вычисляющая N-е число Фибоначчи (как пример рекурсивного алгоритма):

    #include <stdio.h>
    #include <stdlib.h>
    
    unsigned cfib (unsigned n)
    {
      return n < 2 ? n : cfib(n - 1) + cfib(n - 2);
    }
    tfun unsigned fib (unsigned n)
    {
      return n < 20 ? cfib(n) : fib(n - 1) + fib(n - 2);
    }
    tfun int main (int argc, char** argv)
    {
      unsigned n;
      tval unsigned res;
      if (argc < 2) {
        fprintf(stderr, "Usage: fib <number>\n");
        return -1;
      }
      n = (unsigned)atoi(argv[1]);
      res = fib(n);
      printf("fib(%u) = %u\n", n, (unsigned)res);
      return 0;
    }

    На примере этой программы видно, что все отличие в синтаксисе от последовательного варианта заключается в использовании ключевых слов tval и tfun.

    Функция cfib используется для регулировки размера гранулы параллелизма (см. раздел "Размер гранулы параллелизма"). Рекурсия для n < 20 происходит в рамках одного Т-процесса.

3. Вспомогательные макроопределения

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

  1. tdrop — стандартная функция одного аргумента. Может быть вызвана от любой Т-величины. Семантику вызова функции tdrop см. в разделе «Семантика Т++».

  2. twait — стандартная функция двух аргументов: Т-сущности (Т-переменная или Т-указатель) и образца событий. Возвращает статус произошедших с Т-сущностью соответствующих указанному образцу событий. Семантику вызова функции twait см. в разделе «Семантика Т++».

4. Семантика T++

4.1. Т-переменные

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

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

Связь с неготовой величиной разрывается в момент уничтожения Т-переменной (например, при выходе из блока, в котором Т-переменная была объявлена), а также при вызове функции tdrop. При этом Т-переменная связывается с новой, только что созданной неготовой величиной.

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

4.2. Т-функции

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

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

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

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

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

4.3. Т-указатели

Т-указатели являются аналогами C-указателей для неготовых величин. Т-указатели совместимы по большинству операций с обычными указателями; их можно присваивать таким же образом, обращаться за их значением, обращаться к значению с помощью операторов "->" и "*" (результатом применения оператора "*" является неготовая величина).

4.4. Дополнительные функции tdrop и twait

Дополнительные функции tdrop и twait позволяют совершать специфические по отношению к языку C++ операции: разрыв связи с неготовой величиной и ожидание определенных событий (как правило, ожидание готовности одной или нескольких Т-величин).

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

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

При вызове функции twait указывается Т-сущность (обычно Т-величина или массив Т-величин) и образец для ожидаемых событий. Возвращается статус событий. Конкретные образцы могут определяться реализацией языка T++; от реализации требуется поддержка возможности определить статусы готовности одной неготовой величины, а также возможность для получения индексов Т-величин в массиве неготовых величин в порядке наступления их готовности (для реализации недетерминированной альтернативы, что бывает полезно при реализации некоторых переборных алгоритмов).

5. Особенности программирования на языке T++

5.1. Размер гранулы параллелизма

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

5.2. Использование глобальных переменных

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

5.3. Использование С-указателей в Т-переменных

Переменная, которая может содержать неготовое значение объявляется в языке T++ как

tval <type> var;

где tval - ключевое слово языка T++ — признак того, что переменная var может содержать неготовое значение, <type> — C-тип этой переменной.

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

tval char * var;

или

struct example{
  int *fun();
  double num;
};
tval struct example var1;

можно использовать только в том случае, если С-указатель является указателем на функцию или на константный статический объект (такие объекты имеют одинаковые адреса и содержание на всех процессорах). Использовать С-указатель на динамический или автоматический объект в Т-переменной нельзя.

5.4. Ожидание готовности

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

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

6. Создание программ на языке T++

6.1. Структура файла, содержащего программу на языке Т++

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

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

6.2. Возможность предварительной отладки программы в последовательном варианте

Программу на языке T++ можно разрабатывать и отлаживать без использования Т-системы. Для этого достаточно переопределить с помощью макроопределений ключевые слова, добавленные в язык C. Таким образом, для компиляции программы на языке T++ в последовательном режиме, достаточно обрабатывать программу компилятором t++ с опцией -not и программа преобразуется в программу на языке C на этапе препроцессирования.

После того, как программа отлажена в последовательном варианте, можно переходить к ее отладке в параллельном варианте. Для этого используется компилятор t++. При компиляции компилятором t++ без опции -not переопределения ключевых слов не происходит, и программа компилируется для исполнения в параллельном режиме.

6.3. Цикл разработки программ на языке T++

Для наиболее эффективного процесса разработки программ для исполнения под Т-системой следует придерживаться следующей последовательности действий:

  1. Разрабатывается функциональная схема параллельного алгоритма.

  2. Решается вопрос о том, какая часть алгоритма будет реализована на языке T++, а какая — оставлена в виде последовательно исполняемого кода.

  3. Реализуется и отлаживается вся программа на обычном последовательном компьютере.

  4. Программа отлаживается вначале на SMP-компьютере, а затем на реальном кластере.

  5. Снимается профиль программы, производятся различного рода оптимизации.