Введение в программирование на кластерах

Компиляторы

Разбить на страницы
Показывать лекцию целиком

Презентацию к данной лекции Вы можете скачать здесь.

Компиляция - первый шаг в создании процесса

От исходного текста программы к процессу (Linux)

Транслятор выполняет преобразование программы, написанной на одном языке программирования в программу на другом языке программирования.

Компилятор выполняет преобразование программы, написанной на языке программирования (как правило, высокого уровня) в программу на языке машинного уровня.

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

Основные фазы компиляции

Лексический анализ - включает распознавание лексем языка программирования, замену их соответствующими кодами.

Лексемы - элементарные единицы, наделенные определенным "смыслом" и входящие в структуру предложения языка (ключевые или зарезервированные слова, константы, имена и т.д.).

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

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

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

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

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

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

Функции лексического анализатора:

  • ввод строк исходной программы;
  • распознавание лексем;
  • заполнение таблиц идентификаторов и литералов;
  • печать листинга исходной программы.
  • Лексический анализатор (сканер, лексер) учитывает особенности языка программирования. Это включает форматирование исходного текста, интерпретацию пробелов и т.д.

    Лексический анализатор использует:

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

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

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

    Результат работы сканера - последовательность кодов лексем. Код лексемы представляется кодом таблицы и спецификатором. Спецификатор задает номер строки в таблице, куда занесена лексема.

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

    Синтаксис и семантика.

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

    Семантика - множество правил интерпретации смысла предложения.

    Синтаксический анализ - построение дерева грамматического разбора обрабатываемого текста. Текст состоит из предложений.

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

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

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

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

    Результат работы сканера - последовательность кодов лексем. Код лексемы представляется кодом таблицы и спецификатором. Спецификатор задает номер строки в таблице, куда занесена лексема.

    Оптимизирующие преобразования

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

    Удаление общих подвыражений

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

     a = (b+35) * (b+35);
    c = 12.0 * (b+35);

    в результате удаления общего подвыражения превращается в следующий:

    tmp = b + 35;
    a = tmp * tmp;
    c = 12.0 * tmp;

    Развертка цикла

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

    ifor (i = 0; i < 2; i++) a[i] = 2.0 * d[i];

    После развертки:

    a[0] = 2.0 * d[0];
    a[1] = 2.0 * d[1];
    a[2] = 2.0 * d[2];
    

    Удаление инвариантных выражений из тела цикла

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

    for (i = 0; i < 10; i++) {
      a[i] = b * i + 0.35;
    }
    

    преобразуется следующим образом:

    temp = b * 2.0 + 0.35;
    for (i = 0; i < 10; i++) {
    a[i] = temp;}
    

    Подстановка функций

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

    Часто подстановка функции увеличивает эффективность использования кэш-памяти, так как код становится непрерывным.

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

    Свертка и распространение констант

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

    a = b + 2.0 / 3.0;

    Компилятор заменит данный код следующим:

    a = b + 0.666666666666666666667;

    Первый вариант более удобен для программиста, поскольку улучшает читаемость кода.

    Упростить свертку констант компилятором можно, разместив константное выражение в скобках.

    Исключение указателей

    При автоматической оптимизации указатель или ссылка могут быть удалены, если известен их адресат:

    void sub(int * p) {
    *p = *p - 5;}
     int main()
    {
    int a;
     a = 10;
    sub(a);

    В результате преобразования генерируется следующий код:

            a = 10;
            a -= 5;
            

    Объединение ветвей

    Этот вид оптимизации заключается в объединении одинаковых фрагментов кода, например фрагмент кода:

    if (b) { y = x*x; z = y * 1.5; }
    else {
    y = sin(x); z = y * 1.5;
    }
    

    после оптимизации будет выглядеть следующим образом:

      if (b) { y = x*x; }
      else { y = sin(x); }
      z = y * 1.5;
    

    Алгебраическая редукция

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

    a * b + a * c = a * (b + c)
    A + a + a + a = a * 4
    -(-a) = a
    A - a = 0
    (-a) * (-b) = a * b
    a * 1 = a
    a / a = 1
    …
    

    Следует обращать внимание на корректность программы после оптимизации с использованием алгебраической редукции.

    Редукция логических выражений

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

      !(!a) = a
      (ab) || (ac) = a(b||c)
      !a  !b = !(a || b)
      a  !a = false, a || !a = true
      a  true = a, a || false = a
      a  false = false, a || true = true
      a  a = a
      (ab) || (!ac) = a ? b : c
    

    Управление точностью вычислений

    Примеры ключей управления точностью:

    /fp:precise (-fp-model precise)

    /fp:source (-fp-model source)

    /fp:fast (-fp-model fast

    Исключение переходов Объединение ветвей условного оператора Использование регистровых переменных Использование индуктивных переменных Изменение последовательности выполнения операций

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

      float a = -1.0E8, b = 1.0E8, c = 1.23456, y;
      y = a + b + c;
      printf("%f\n", y);
      y = b + c + a;
      printf("%f", y);
    

    оба результата – разные. ...

    Другие виды автоматической оптимизации

  • Оптимизация под архитектуру
  • Распараллеливание
  • Оптимизация с профилированием
  • Межпроцедурная оптимизация.
  • и т.д.

    При автоматической оптимизации может измениться поведение программы

    Пример изменения поведения программы после оптимизации уровня /O2

      bool func()
      {  
        int a(1000), b(0);
    
        for(int i = 0; i < 10000; i++)
        b = (a-i)/(a-i)*b;
    
        return false;
      }
    

    Компиляторы

    Intel - компиляторы C/C++, Fortran. Включены в состав различных пакетов ПО для профессионального разработчика.

    Поддерживаются платформы Microsoft Windows и Linux.

    Поддерживается работа с оптимизированными библиотеками: Intel®MKL, Intel®IPP.

    Эффективная оптимизация для архитектур Intel.

    Улучшенная поддержка векторизации (увеличенная разрядность векторных инструкций).

    Поддержка Fortran 2008.

    Интеграция с Microsoft Visual Studio.

    Ссылка на ресурс: http://software.intel.com

    GNU Compiler Collection (GCC) - компиляторы C/C++, Объектный C, Fortran, Ada, Java. Поддерживается платформа Linux. Распространяется свободно, в том числе в исходных кодах.

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

    Поддерживается Intel® CilkTM Plus.

    Ссылка на ресурс: http://gcc.gnu.org/

    Oracle Solaris Studio - компиляторы C/C++ и Fortran. Поддерживаются платформы Linux и Solaris, архитектуры x86 и SPARC.

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

    Ссылка на ресурс: http://www.oracle.com

    PGI Workstation - компиляторы C/C++ и Fortran. Поддерживаются платформы Linux, Microsoft Windows и MacOS, архитектуры x86, AMD и CUDA.

    Поддерживаются различные варианты оптимизации, автоматического распараллеливания, отладки, интеграция с Microsoft Visual Studio.

    Fortran 2003.

    Ссылка на ресурс: http://www.pgroup.com/

    NAG (Numerical Algorithms Group) Fortran Compiler - компилятор Fortran. Поддерживаются платформы Unix, Microsoft Windows и MacOS.

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

    Fortran 2003/2008.

    Ссылка на ресурс: http://www.nag.co.uk

    Microsoft Visual C++ - поддерживается платформа Microsoft Windows.

    Интеграция в Microsoft Visual Studio.

    Ссылка на ресурс: http://www.microsoft.com

    Другие компиляторы (в том числе в статусе исследовательских проектов)

    Open64

    Ссылка на ресурс: http://www.open64.net

    Clang

    Ссылка на ресурс: http://clang.llvm.org

    Portable C compiler

    Ссылка на ресурс: http://pcc.ludd.ltu.se/

    и другие.

    Страницы:

    Презентацию к данной лекции Вы можете скачать здесь.

    Компиляция - первый шаг в создании процесса

    От исходного текста программы к процессу (Linux)

    Транслятор выполняет преобразование программы, написанной на одном языке программирования в программу на другом языке программирования.

    Компилятор выполняет преобразование программы, написанной на языке программирования (как правило, высокого уровня) в программу на языке машинного уровня.

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

    Основные фазы компиляции

    Лексический анализ - включает распознавание лексем языка программирования, замену их соответствующими кодами.

    Лексемы - элементарные единицы, наделенные определенным "смыслом" и входящие в структуру предложения языка (ключевые или зарезервированные слова, константы, имена и т.д.).

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

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

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

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

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

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

    Функции лексического анализатора:

  • ввод строк исходной программы;
  • распознавание лексем;
  • заполнение таблиц идентификаторов и литералов;
  • печать листинга исходной программы.
  • Лексический анализатор (сканер, лексер) учитывает особенности языка программирования. Это включает форматирование исходного текста, интерпретацию пробелов и т.д.

    Лексический анализатор использует:

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

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

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

    Результат работы сканера - последовательность кодов лексем. Код лексемы представляется кодом таблицы и спецификатором. Спецификатор задает номер строки в таблице, куда занесена лексема.

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

    Синтаксис и семантика.

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

    Семантика - множество правил интерпретации смысла предложения.

    Синтаксический анализ - построение дерева грамматического разбора обрабатываемого текста. Текст состоит из предложений.

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

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

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

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

    Результат работы сканера - последовательность кодов лексем. Код лексемы представляется кодом таблицы и спецификатором. Спецификатор задает номер строки в таблице, куда занесена лексема.

    Оптимизирующие преобразования

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

    Удаление общих подвыражений

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

     a = (b+35) * (b+35);
    c = 12.0 * (b+35);

    в результате удаления общего подвыражения превращается в следующий:

    tmp = b + 35;
    a = tmp * tmp;
    c = 12.0 * tmp;

    Развертка цикла

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

    ifor (i = 0; i < 2; i++) a[i] = 2.0 * d[i];

    После развертки:

    a[0] = 2.0 * d[0];
    a[1] = 2.0 * d[1];
    a[2] = 2.0 * d[2];
    

    Удаление инвариантных выражений из тела цикла

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

    for (i = 0; i < 10; i++) {
      a[i] = b * i + 0.35;
    }
    

    преобразуется следующим образом:

    temp = b * 2.0 + 0.35;
    for (i = 0; i < 10; i++) {
    a[i] = temp;}
    

    Подстановка функций

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

    Часто подстановка функции увеличивает эффективность использования кэш-памяти, так как код становится непрерывным.

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

    Свертка и распространение констант

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

    a = b + 2.0 / 3.0;

    Компилятор заменит данный код следующим:

    a = b + 0.666666666666666666667;

    Первый вариант более удобен для программиста, поскольку улучшает читаемость кода.

    Упростить свертку констант компилятором можно, разместив константное выражение в скобках.

    Исключение указателей

    При автоматической оптимизации указатель или ссылка могут быть удалены, если известен их адресат:

    void sub(int * p) {
    *p = *p - 5;}
     int main()
    {
    int a;
     a = 10;
    sub(a);

    В результате преобразования генерируется следующий код:

            a = 10;
            a -= 5;
            

    Объединение ветвей

    Этот вид оптимизации заключается в объединении одинаковых фрагментов кода, например фрагмент кода:

    if (b) { y = x*x; z = y * 1.5; }
    else {
    y = sin(x); z = y * 1.5;
    }
    

    после оптимизации будет выглядеть следующим образом:

      if (b) { y = x*x; }
      else { y = sin(x); }
      z = y * 1.5;
    

    Алгебраическая редукция

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

    a * b + a * c = a * (b + c)
    A + a + a + a = a * 4
    -(-a) = a
    A - a = 0
    (-a) * (-b) = a * b
    a * 1 = a
    a / a = 1
    …
    

    Следует обращать внимание на корректность программы после оптимизации с использованием алгебраической редукции.

    Редукция логических выражений

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

      !(!a) = a
      (ab) || (ac) = a(b||c)
      !a  !b = !(a || b)
      a  !a = false, a || !a = true
      a  true = a, a || false = a
      a  false = false, a || true = true
      a  a = a
      (ab) || (!ac) = a ? b : c
    

    Управление точностью вычислений

    Примеры ключей управления точностью:

    /fp:precise (-fp-model precise)

    /fp:source (-fp-model source)

    /fp:fast (-fp-model fast

    Исключение переходов Объединение ветвей условного оператора Использование регистровых переменных Использование индуктивных переменных Изменение последовательности выполнения операций

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

      float a = -1.0E8, b = 1.0E8, c = 1.23456, y;
      y = a + b + c;
      printf("%f\n", y);
      y = b + c + a;
      printf("%f", y);
    

    оба результата – разные. ...

    Другие виды автоматической оптимизации

  • Оптимизация под архитектуру
  • Распараллеливание
  • Оптимизация с профилированием
  • Межпроцедурная оптимизация.
  • и т.д.

    При автоматической оптимизации может измениться поведение программы

    Пример изменения поведения программы после оптимизации уровня /O2

      bool func()
      {  
        int a(1000), b(0);
    
        for(int i = 0; i < 10000; i++)
        b = (a-i)/(a-i)*b;
    
        return false;
      }
    

    Компиляторы

    Intel - компиляторы C/C++, Fortran. Включены в состав различных пакетов ПО для профессионального разработчика.

    Поддерживаются платформы Microsoft Windows и Linux.

    Поддерживается работа с оптимизированными библиотеками: Intel®MKL, Intel®IPP.

    Эффективная оптимизация для архитектур Intel.

    Улучшенная поддержка векторизации (увеличенная разрядность векторных инструкций).

    Поддержка Fortran 2008.

    Интеграция с Microsoft Visual Studio.

    Ссылка на ресурс: http://software.intel.com

    GNU Compiler Collection (GCC) - компиляторы C/C++, Объектный C, Fortran, Ada, Java. Поддерживается платформа Linux. Распространяется свободно, в том числе в исходных кодах.

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

    Поддерживается Intel® CilkTM Plus.

    Ссылка на ресурс: http://gcc.gnu.org/

    Oracle Solaris Studio - компиляторы C/C++ и Fortran. Поддерживаются платформы Linux и Solaris, архитектуры x86 и SPARC.

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

    Ссылка на ресурс: http://www.oracle.com

    PGI Workstation - компиляторы C/C++ и Fortran. Поддерживаются платформы Linux, Microsoft Windows и MacOS, архитектуры x86, AMD и CUDA.

    Поддерживаются различные варианты оптимизации, автоматического распараллеливания, отладки, интеграция с Microsoft Visual Studio.

    Fortran 2003.

    Ссылка на ресурс: http://www.pgroup.com/

    NAG (Numerical Algorithms Group) Fortran Compiler - компилятор Fortran. Поддерживаются платформы Unix, Microsoft Windows и MacOS.

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

    Fortran 2003/2008.

    Ссылка на ресурс: http://www.nag.co.uk

    Microsoft Visual C++ - поддерживается платформа Microsoft Windows.

    Интеграция в Microsoft Visual Studio.

    Ссылка на ресурс: http://www.microsoft.com

    Другие компиляторы (в том числе в статусе исследовательских проектов)

    Open64

    Ссылка на ресурс: http://www.open64.net

    Clang

    Ссылка на ресурс: http://clang.llvm.org

    Portable C compiler

    Ссылка на ресурс: http://pcc.ludd.ltu.se/

    и другие.

    Вернуться к учебному плану