1.4 Компиляторы. Интерактивные системы.
Компиля́ция — трансляция программы, составленной на исходном языке высокого уровня, в эквивалентную программу на низкоуровневом языке, близком машинному коду (абсолютный код, объектный модуль, иногда на язык ассемблера). Входной информацией для компилятора (исходный код) является описание алгоритма или программа на объектно-ориентированном языке, а на выходе компилятора — эквивалентное описание алгоритма на машинно-ориентированном языке (объектный код).
GNU Compiler Collection (обычно используется сокращение GCC) — набор компиляторов для различных языков программирования, разработанный в рамках проекта GNU. Он используется как стандартный компилятор для свободных UNIX-подобных операционных систем.
Изначально названный GNU C Compiler поддерживал только язык Си. Позднее GCC был расширен для компиляции исходных кодов на таких языках программирования, как C++, Objective-C, Java, Фортран, Ada и Go.
Начало GCC было положено Ричардом Столлманом, который реализовал первый вариант GCC в 1985 году на нестандартном и непереносимом диалекте языка Паскаль; позднее компилятор был переписан на языке Си, как компилятор для проекта GNU, который сам по себе являлся свободным программным обеспечением.
ТИПЫ ОПТИМИЗАЦИЙ.
• оптимизации под типы приложений (оптимизация размера исполняемого кода, времени выполнения без ущерба для его размера и только времени выполнения)
• процессорные оптимизации (планирование инструкций, оптимизация под конкретный процессор, автоматическая и ручная диспетчеризация процессоров)
• дополнительные оптимизации (управление точностью вычислений с плавающей точкой, математические библиотеки стандартных функций)
ОПЦИИ ОПТИМИЗАЦИИ
• /Od (Linux: -O0) • Отключает возможности оптимизации • Следует использовать для проведения отладки и быстрой компиляции • Данную функцию можно использовать на начальных стадиях разработки приложения до того момента, когда Вы убедитесь в его корректной работе.
• /O1 (Linux: -O0) Оптимизация скорости пока есть благоприятствование размеру кода • O1 может повысить производительность приложений с кодом большого размера и множеством разветвлений, а также сократить время выполнения, устранив код внутри циклов • В большинстве случаев обеспечивает создание кода наименьшего размера • Эта функция применяется для приложений с высокими требованиями к размеру кода и его размещению, как, например, приложения больших баз данных и коды со множеством разветвлений без
использования циклов
• /O2 (Linux: -O2) По умолчанию. Оптимизация скорости приложения. Рекомендуемый уровень • В большинстве случаев создает самый быстрый код, но может увеличить код до размера, значительно превышающего -O1 • Для наилучшей общей производительности при работе с типичными приложениями целочисленных вычислений, которые мало используют вычисления с плавающей запятой
• /O3 (Linux: -O3) (Оптимизации высокого уровня) Включает /O2 на ряду с более агрессивными оптимизациями
• Иногда может замедлить код в сравнении с /O2 • Рекомендуется для приложений с циклами, интенсивно использующими вычисления с плавающей точкой, и для обработки больших массивов данных • Использование этой функции может увеличить время компиляции • Повышение производительности зависит от типа приложения; производительность некоторых приложений может остаться прежней
• -fast Увеличивает скорость работы всей программы:
i64 Linux: Поддерживает -O3 -ipo -static
i64 Windows: Поддерживает -O3 -Qipo (Qipo ~ ipo)
i32 Linux: Поддерживает -O3 -ipo -static -xP (-xP – векторизация)
i32 Windows: Поддерживает -O3 -Qipo -QxP
IPO (Interprocedural Optimization) – межпроцедурный анализ и оптимизации, которые делает компилятор над нашим кодом. Подключается он опцией -ipo и позволяет проводить оптимизации не для одного отдельного файла с исходным кодом, а для всех исходников одновременно. В этом случае компилятор знает намного больше и может сделать значительно больше выводов и, соответственно, преобразований/оптимизаций. При компиляции с -ipo меняется привычный нам порядок компиляции и линковки.
• -ip (между процедурами) • -ipo (между процедурами и между исходными файлами)
-static – Предотвращает линкование с общими библиотеками
ОТЛАДКА
• Используйте опции /Zi (Windows) и –g (Linux) для генерации информации символьной отладки (symbolic debugging), которую используют инструментальные средства анализа производительности. • Создает информацию по отладке • Добавляет информацию в объектный файл с тем, чтобы любая отдельная сборочная линия могла быть связана с соответствующим кодом • Выберите эту функцию наряду с требуемым вариантом оптимизации, например, -O2. В противном случае -Od (Linux: -O0) выбирается по умолчанию
ПРОЦЕССОРНЫЕ ОПТИМИЗАЦИИ (icl – компилятор Intel)
• Можно получать код, оптимизированный под инструкции конкретного процессора icl /QxB myprog.cpp
• На других процессорах код не запуститься: будет выдано сообщение об ошибке
Для подключения оптимизаций, специфичных для процессоров Intel, можно использовать опцию -x'code'. Она говорит компилятору, какие процессорные возможности можно использовать, включая набор инструкций, которые могут быть сгенерированы. В качестве 'code' можно задавать SSE2, SSE3, SSSE3, SSE3_ATOM и SSSE3_ATOM, ATOM_SSSE3, AVX, CORE-AVX-I и т.д. Особо ленивые могут использовать опцию -xHost, и в этом случае оптимизации будут делаться под железо, на котором мы собираем код.
/G1 - Оптимизация для процессора Itanium. Доступна только в кросс-компиляторе Itanium или в компиляторе машинного кода Itanium.
/G2 - Оптимизация для процессора Itanium2 (значение по умолчанию находится между /G1 и /G2) Доступна только в кросс-компиляторе Itanium или в компиляторе машинного кода Itanium.
ОПТИМИЗАЦИЯ С ПЛАВАЮЩЕЙ ЗАПЯТОЙ
• В большинстве случаев при увеличении точности вычислений с плавающей запятой также возрастает время
выполнения различных процессов.
• -Op (-mp Linux) Строгие вычисления с плавающей запятой (подмножество -Za или -ansi) характеризуются высокой точностью, которая замедляет работу приложения
• -Za (-ansi Linux) Строгие вычисления. Поддерживает меньшую скорость и более высокую точность, чем -Op
• -Qlong_double - В Linux не доступно. Изменяет размер по умолчанию чисел типа long double с 64 до 80 бит. Данная функция вызывает ряд несоответствий с другими файлами, скомпилированными без использования данной функции, при обращении к подпрограмме библиотеки. Следовательно, корпорация Intel рекомендует использовать переменные long double в одном файле при компиляции с использованием этой функции.
• -Qprec (-mp1 Linux) Только для систем на базе архитектуры IA-32. Почти такая же точность и скорость, как у ANSI. Все наиболее трудоемкие операции проводятся в соответствии с ANSI
• -Qprec_div (-prec_div Linux) Только для систем на базе архитектуры IA-32 . По умолчанию компилятор Intel® преобразовывает операции деления в обратные операции умножения, что снижает точность, но повышает скорость работы.
• -Qpc{32|64|80} (-pc{32|64|80} Linux) Только для систем на базе архитектуры IA-32 . Указывает требуемое точное количество битов: 32, 64, or 80. Округляется до n где n=[32|64|80]
• -Qrcd (-rcd Linux) Только для систем на базе архитектуры IA-32. Удаляет код, усеченный во время превращения
вычислений с плавающей запятой в целочисленные вычисления
МАТЕМАТИЧЕСКИЕ БИБЛИОТЕКИ
• Компиляторы Intel® поддерживают две библиотеки, которые способствуют созданию более эффективного кода. Обе библиотеки включены путем добавления их месторасположения в путь, указанный в переменной среды LIB, и поддерживают автоматическое использование при обращении к одной из функцией векторизации.
• Иногда обеспечивается более высокая скорость, чем при использовании libc.lib и libm.lib Microsoft и libm.a GNU*. • Автоматически заменяет многие функции в ОС Microsoft libc.lib и libm.lib и GNU libm.a. • Создает оптимальный код для всех процессоров на базе архитектуры Intel | gcc code.c -lm
СИСТЕМНЫЕ МЕТОДЫ ОПТИМИЗАЦИИ
• Идея – оптимизация кода на языке высокого уровня при его компиляции • Эти методы не меняют исходного кода, но для использования накладывают на него определённые условия и требуют аккуратного использования
ВЕКТОРИЗАЦИЯ
• Векторизатор увеличивает скорость выполнения кода, используя расширенные инструкции процессора – Single-Instruction Multiple-Data (SIMD) для циклов • Компилятор поддерживает генерацию команд SIMD процессоров с технологией Intel MMX, а также с набором команд Streaming SIMD Extensions: SSE, SSE2, SSE3, SSE4, SSE4.1, SSE4.2, AVX • Пример: gcc.exe /xP superprog.cpp
1) Необходимое свойство для векторизации кода: над элементами однотипных данных производятся однотипные операции 2) Формирование из скалярных данных вектора — запись данных в векторные регистры (используется инструкция из расширения) 3) Применение к этим векторам векторной операции (задействование дополнительного оборудования на процессоре — всех или нескольких АЛУ) 4) Сохранение результата из векторного регистра в память
● Векторизуемые данные должны располагаться последовательно в памяти ● Требует независимость итераций ● Требует устранение противоречий памяти ● Из-за использования специального набора команд (SSEx, AVXx), машинный код будет исполняться только на поддерживающих эти инструкции процессорах
ОПТИМИЗАЦИЯ ВЫСОКОГО УРОВНЯ
• High Level Optimizer (HLO) • HLO ориентирован на максимальную скорость работы • Может переписать алгоритм, чтобы максимально увеличить количество успешных обращений к кэш-памяти, осуществляя более агрессивный анализ зависимости по данным • Пример: gcc -O3 superprog.cpp
• Циклы должны удовлетворять тем же требованиям, что и для векторизации • HLO необязательно повысит производительность некоторых приложений. - разворачивание циклов!
IPO – МЕЖПРОЦЕДУРНАЯ ОПТИМИЗАЦИЯ (было выше)
ВЕДОМАЯ ПРОФИЛЕМ ОПТИМИЗАЦИЯ
• Profile Guided Optimization (PGO) • Такую компиляцию можно назвать – динамической – замкнутой – или компиляцией с обратной связью • Предоставляет компилятору информацию о динамическом управлении программой для выполнения наилучших вариантов оптимизаций • Используются различные статистические и вероятностные методы • Приложение будет работать быстрее при типичной нагрузке • Лучше использовать PGO в приложениях со множеством функций, вызовов или ветвлений, не связанных с циклами • Требует типичной рабочей нагрузки – не для исключительных случаев -fprofile_generate / -fprofile_use
- Для осуществления встраивания функций необходимо использовать PGO. Так как это оптимизация с обратной связью, мы сначала используем опцию –prof_gen для генерации «специального» исполняемого модуля, по которому будет создаваться файл *.dpi (Инструментальная компиляция). Осуществляем тестовый запуск программы (Можем несколько раз с различными входными данными). Затем используем опцию –prof_use для создания оптимизированного исполняемого модуля (Компиляция с обратной связью).
ПРОБЛЕМЫ ВЫСОКОПРОИЗВОДИТЕЛЬНЫХ ВЫЧИСЛЕНИЙ
- стены памяти
● Проблема «стены памяти» – это резкое падение реальной производительности из-за простоев процессора, связанных с чрезвычайно неэффективной работой подсистемы памяти. ● Разница в пропускной способности памяти и процессора монотонно растёт
Решения:
● Поводить анализ пространственно-временной локализации для каждой операции чтения/запись. ● Использовать асинхронное, или отложенное, чтение/запись в память для операций с плохой локализацией ● Применять легковесные потоки на основе сопрограмм для реализации асинхронности операций.
● Пространственная локализация (Spatial Locality) определяется близостью адресов данных в памяти.
• Временная локализация (Temporal Locality) определяется частотой обращения к одним и тем же адресам
- организация «миллионных» вычислений
● При увеличении ядер производительность перестаёт расти — проблема масштабируемости ● Число ядер будет возрастать всё быстрее ● Например, в задачах гидродинамики ядра 99,99% времени занимаются обменом данными, а задачу необходимо решать в сотни раз быстрее
- проблема надежности
● Большее число ядер — выгоднее энергетически ● НО при числе ядер ~ О(106) вероятность отказоустойчивой работы всех ядер крайне мала.
Решения:
● Нужна новая парадигма независимых вычислений ● Вычисления на ядрах должны быть слабосвязанными — практически не обмениваться информацией — при миллионных вычислениях обмен будет допустимым ● Часть ядер может получать даже неверные результаты, но в целом вычисления сводятся к верному решению ● Вычисления не должны зависеть от вышедших их строя ядер и узлов ● Вычисления должны использовать иерархическую организацию памяти.
