Входной язык TC

Язык TC создавался в качестве входного языка Т-системы [GRACE] как синтаксически и семантически "гладкое" расширение языка C. Под "гладкостью" здесь понимается то, что дополнительные конструкции синтаксически и семантически увязаны с конструкциями основного языка C.

Выражаясь математическим языком, хотелось сделать коммутативной следующую диаграмму:


        Последовательный компьютер  ------->   Язык C
 
                    |                             |
                    |                             |
                    |                             |
                    |                             |
                    V                             V

        Параллельный компьютер      ------->   Языки TC

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

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

В то же время эти расширения выглядят достаточно прозрачными для синтаксиса и семантики языка C: программу на языке TC можно разрабатывать и отлаживать без использования Т-системы. Для этого достаточно переопределить с помощью макроопределений ключевые слова, добавленны в язык C.

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

Пример программы на языке TC

Рассмотрим в качестве примера вычисление программу на языке TC, вычисляющую N-е число Фиббоначи наиболее неэффективным способом:

#include <tc.h>

#include <stdio.h>
#include <stdlib.h>

unsigned cfib (unsigned _n)
{
  return _n < 2 ? _n : cfib(_n - 1) + cfib(_n - 2);
}

tfun void fib (unsigned _n, tout unsigned *_res)
{
  if (_n < 20) {
    *_res = cfib(_n);
  } else {
    tval unsigned res1, res2;
    fib(_n - 1, &res1);
    fib(_n - 2, &res2);
    *_res = res1 + res2;
  }
}

int tmain (int argc, char* argv[])
{
  unsigned n;
  tval unsigned res;
  if (argc < 2) {
    fprintf(stderr,
      "Usage:\n"
      "gr_fib <number>\n"
    );
    return -1;
  }
  n = (unsigned)atoi(argv[1]);
  fib(n, &res);
  printf("fib(%u) = %u\n", n, res);
  return 0;
}

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

Структура программы на языке TC

Программа на языке TC должна начинаться включением загаловочного файла <tc.h>

Затем идут включения других загаловочных файлов, причем те файлы, которые содержат TC-конструкции, должны включаться способом 1, а файлы, не предназначенные для препроцессирования конвертером TC2C -- способом 2

  1. #include "file.h"
  2. #include <file.h>

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

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

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

Дополнительные ключевые слова языка TC

  1. tval -- атрибут, указываемый в определениях "неготовых" данных.
  2. tptr -- указатель на Т-обЪект, который может находится на другом узле кластера.
  3. tout -- атрибут, указываемый в прототипе Т-функций у результатов.
  4. tfun -- признак того, что определяемая функция может вычисляться в параллель.

Заголовочный файл <tc.h>

#ifdef TSYSTEM
// grace.hh required for parallel execution
#ifdef __cplusplus
#include "grace/grace.hh"
#endif
#define TCALL(texpr,tinfo) { CallInfo call_info_default tinfo; texpr; }
#define talloc(size) (shared_allocator->allocate(size))
#define tfree(ptr) (shared_allocator->deallocate(ptr))
#define tnew(type) new T_Val<type>
#define tnewarray(type,n) new T_Val<type>[n]
#define tdelete(ptr) delete ptr
#define tdeletearray(ptr) delete[] ptr 
#define tvalidate(val) ((val).validate())
#define abort grace::abort
#define tptrwait(x) (x).wait()
#define atexit grace::atexit
#else // TSYSTEM
// Dummy stuff for sequential TC-program execution
#define tfun
#define tval
#define tout
#define tptr *
#define tmain main
#define TCALL(texpr,tinfo) texpr
#define talloc malloc
#define tfree  free
#define tnew(type) ((type*)malloc(sizeof(type)))
#define tnewarray(type,n) ((type*)malloc(n*sizeof(type)))
#define tdelete(ptr) free(ptr)
#define tdeletearray(ptr) free(ptr)
#define tvalidate(val) (val)
#define tprint printf
#define terrprint(x...) fprintf(stderr, x)
#define tptrwait(x) do{}while(0)
#define finish() exit(0)
#ifdef DEBUG
#define dmesg  printf
#else
#define dmesg(x...)
#endif
#define vmesg  printf
#endif // TSYSTEM

Комментарии к загаловочному файлу <tc.h>

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

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

  1. #define TCALL(texpr,tinfo) { CallInfo call_info_default tinfo; texpr; }

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

    Допускаются любые комбинации этих двух возможностей.

    Пример использования:

    TCALL((f(a,b,&c),(0,GR_CI_FIXED|GR_CI_SYSTEM))
    
    -- Позвать функлцию f, принимающую аргументы a,b и возвращающую результат
       c на нулевом узле (в нумерации MPI), в контексте системного процесса
       (т.е. для выполнения обращений к невычислиытельным системным библиотекам)
    

  2. #define talloc(size) (shared_allocator->allocate(size))

    Этот макрос следует использовать для захвата памяти вместо malloc.

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

    Пример использования:

    struct mystruct *p = talloc(sizeof mystruct)
    
    -- захватить память под структуру языка C mystruct
    
    

  3. #define tfree(ptr) (shared_allocator->deallocate(ptr))

    Парная функция для освобождения памяти для talloc.

    Замечание: в скором времени будет реализована поддержка неявного освобождения памяти (автоматическая распределенная сборка мусора)

    Пример использования:

    
    tfree(p);
    
    -- освободить память, захваченную ранее.
    

  4. #define tnew(type) new T_Val<type>

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

    Это необходимо делать постольку, поскольку соответсвующие оберточные классы C++ имеют непустые конструкторы, и, соответсвенно, требуется их корректная инициализация.

    Пример использования:

    
    struct line_tptr {
      struct line tptr v;
    };
    
    struct line_tptr_array {
      struct line_tptr a[N];
    };
    
        struct line_tptr_array tptr aa =
    	tnew(struct line_tptr_array);
    
    Здесь вложеннся структура line_tptr имеет поле с атрибутом  tptr,
    поэтому требуется ее захват с помощью tnew.
    

  5. #define tnewarray(type,n) new T_Val<type>[n]

    Тоже, что и предыдущий макрос, но для массива T-структур.

    Пример использования:

    tnewarray(struct line_tptr, 20)
    
    Захватить 20 неготовых значений
    

  6. #define tdelete(ptr) delete ptr

    Удаление захваченной в куче памяти Т-структуры

    Пример использования:

    tdelete(p)
    

  7. #define tdeletearray(ptr) delete[] ptr

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

    Пример использования:

    tdeletearray(p);
    

  8. #define tvalidate(val) ((val).validate())

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

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

    Пример использования:

    tvalidate(*p);
    

  9. #define abort grace::abort

    Корректное аварийное завершение параллельной программы.

    Пример использования:

    abort();
    

  10. #define tptrwait(x) (x).wait()

    Явное ожидание Т-значения, на которое указывает неконстантный Т-указатель.

    Временно введенная возможность, которая является излишней и вскоре будет удалена.

    Пример использования:

    tptrwait(p);
    

  11. #define atexit grace::atexit

    Регистрация функции - финализатора.

    Пример использования:

    atexit(my_terminator);
    

Цикл разработки программ на языке TC

  1. Разрабатывается функциональная схема параллельного алгоритма
  2. Решается вопрос о том, какая часть будет реализована на языке TC, а какая - оставлена в виде последователно исполняемого кода.
  3. Реализуется и отлаживается вся программа на обычном последовательном компьютере.
  4. Программа отлаживается вначале на SMP-компьютере, затем на реальном кластере.
  5. Снимается профиль программы, производится различного рода оптимизация.