Программирование на кластерах с использованием инструментов Intel (Intel Cluster Studio)

Компиляторы Intel. Возможности автоматической оптимизации

Показывать лекцию целиком

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

Компиляторы Intel

Intel® Composer XE, Intel® Parallel Studio XE, Intel® Cluster Studio, Intel® C++ Studio XE, Intel® Fortran Composer

и некоторые другие продукты включают в свой состав компиляторы C/C++, Fortran,

высокопроизводительные библиотеки Intel и другие программные инструменты.

Основные факты о компиляторах Intel

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

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

Средства поддержки различных типов оптимизации.

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

Интеграция в среды разработки:

Совместимость с Microsoft Visual C, компиляторами GCC (Linux) и MacOS.

Поддержка Fortran 77 – 2008. Поддержка COARRAY, DO CONCURRENT, а также объектно-ориентированного программирования в Fortran 2008.

Подробная диагностика, генерация отчётов об оптимизации.

Запуск

icl (MS Windows) – C/C++

icc (Linux) – C/C++

ifort (MS Windows и Linux) – Fortran

Перед использованием компиляторов в режиме CLI (Command Line Interface) требуется запуск командных файлов,

выполняющих установку маршрутных имён исполняемых файлов, а также других параметров.

Ключи в разных операционных системах аналогичны. Несовместимые ключи игнорируются.

Векторизация

Векторные команды (SIMD)

AVX – набор векторных команд

Intel® AVX (Advanced Vector Extensions) расширяет возможности 128-разрядных векторных регистров XMM (16 регистров), позволяя использовать 256-разрядные векторы. 256 разрядов используются в операциях с плавающей точкой.

В других операциях используются младшие 128 разрядов.

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

AVX для архитектуры Sandy Bridge

Поддержка векторных конструкций в Intel® CilkTM Plus и Array Building Blocks.

Поддержка прагм и "интринсиков" (низкоуровневые возможности).

Поддержка сложных условных выражений.

Улучшенная поддержка смешения типов в одном цикле.

Поддержка операций с насыщением.

Отчёты об оптимизации

Отчёты об оптимизации

  • /Qvec-report - генерация протокола векторизации (что векторизовано, что нет и почему).
  • /Qpar-report - генерация протокола распараллеливания.
  • /Qopt-report - генерация протокола оптимизации.
  • Профилирование на уровне циклов

    Сбор статистики по циклам и функциям

  • /Qprofile-loops:all - сбор статистики.
  • LoopProfileViewer - утилита для просмотра отчётов.
  • GAP - Guided Auto Parallelism (направляемая автопараллелизация).
  • /Qguide (-guide) - запуск анализа.
  • Не порождает параллельный код, но даёт рекомендации.
  • Оптимизация в примерах

    /Od (Windows), -O0 (Linux)

  • Отключение оптимизации
  • Подразумевается в режиме Debug в MS Visual Studio
  • /O1 (Windows), -O1 (Linux)

  • Глобальная оптимизация
  • Не увеличивает размер кода
  • /O2 (Windows), -O2 (Linux)

  • Увеличивает размер кода
  • Оптимизация времени выполнения
  • Опция по умолчанию
  • Подстановки в коде
  • Развертывание циклов
  • Векторизация
  • /O3 (Windows), -O3 (Linux)

  • Высокоуровневая оптимизация.
  • Оптимизирующие преобразования уровня /O2 + более агрессивные методы.
  • Улучшенная векторизация.
  • Более полный учёт свойств циклов и массивов.
  • Оптимизация циклов: разделение циклов (loop distribution), перестановка циклов (loop interchange), слияние циклов (loop fusion), развёртка циклов (loop unrolling).
  • Подстановка кода в ветвлениях.
  • Оптимизация под размер кэша.
  • Предвыборка и предсказания ветвления.
  • Возможна большая эффективность в приложениях, включающих обработку больших массивов.
  • Пример 1

    Matrices.cpp

     
    #include <ctime>
        #include <iostream>
            #include "stdlib.h"
            #include "conio.h"
            #include "math.h"
            
            using namespace std;
            const int SIZE = 1000;
            
            void matrixMultiply(double ** matrA, double ** matrB, double ** matrC, int matrSize);
            double sinCosMultiply(double i, double j);
            
            int main()
            {
            time_t t1, t2;
            double trace(0);
            double ** matrixA = new double*[SIZE];
            double ** matrixB = new double*[SIZE];
            cout << "We will construct the square matrix " << SIZE << "x" << SIZE << endl;
            cout << "of pseudo-random values" << endl;
            cout << "and multiply it by itself. Then we will find the trace of the matrix." << endl;
            for(int i = 0; i < SIZE; i++)
            {
            matrixA[i] = new double[SIZE];
            matrixB[i] = new double[SIZE];
            }
            t1 = clock();
            for(int i=0; i<SIZE; i++)
            {
            for(int j=0; j<SIZE; j++)
                {
                matrixA[i][j]=sinCosMultiply(rand()%10/3, rand()%10/3);
                }}
                matrixMultiply(matrixA, matrixA, matrixB, SIZE);
                for(int i=0; i<SIZE; i++)
                {
                for(int j=0; j<SIZE; j++)
                    {
                         if(i==j) trace += matrixB[i][j];
                         }
                         }
                         t2 = clock();
                         cout << "\nTrace is " << trace << endl;
                         cout << "\nInitial clock ticks value is " << t1 << endl;
                         cout << "Final clock ticks value is " << t2 << endl;
                         cout << "Difference in clock ticks is " << difftime(t2,t1) << endl;
                         cout << "Clock ticks per second value is " << CLOCKS_PER_SEC << endl;
                         cout << "\nActual time of calculations is " << ((t2-t1)/CLOCKS_PER_SEC) << " sec" << endl;
                         for(int i = 0; i < SIZE; i++)
                         {
                         delete [] matrixA[i];
                         delete [] matrixB[i];
                         }
                         delete [] matrixA;
                         delete [] matrixB;
                         return 0;
                         }
                         void matrixMultiply(double ** matrA, double ** matrB, double ** matrC, int matrSize)
                         {
                         for(int i=0; i<matrSize; i++)
                         {
                         for(int j=0; j<matrSize; j++)
                             {
                             matrC[i][j] = 0;
                             for(int k=0; k<matrSize; k++)
                             {
                             matrC[i][j] += matrA[i][k]*matrB[k][j];
                             }
                         }
                   }
               }
                             
         double sinCosMultiply(double i, double j)
           {
           return sin(i)*cos(j);
            }    
            

    Подготовим тест:

  • icl /FeMatricesOd /Od Matrices.cpp
  • icl /FeMatricesO1 /O1 Matrices.cpp
  • icl /FeMatricesO2 /O2 Matrices.cpp
  • icl /FeMatricesO3 /O3 Matrices.cpp
  • Intel Core 2 Duo T3700 2.00 ГГц, 2 Гб RAM

    Среднее значение времени выполнения:

  • MatricesOd . . . 21.76 с.
  • MatricesO1 . . . 14.05 с.
  • MatricesO2 . . . 9.63 с.
  • MatricesO3 . . . 9.57 с.
  • Вопрос. Объясните результаты теста.

    Оптимизация под архитектуру

    /Qax (Windows), -ax (Linux)

  • Оптимизация под архитектуру Intel (использование векторных расширений инструкций)
  • QxHost
  • QxAVX
  • QxSSE2, QxSSE3, QxSSE3_ATOM, QxSSE4.1, QxSSE4.2, QxSSSE3
  • Пример 2

    Primes.cpp

    #include "time.h"
    #include <iostream>
        #include "stdlib.h"
        #include "conio.h"
        #include "math.h"
        
        using namespace std;
        const int NUM_OF_TESTS = 2000000;
        const int RANGE = 1000;
        bool isComposite(const int y);
        
        int main()
        {
        time_t t1, t2;
        int counter(0);
        t1 = clock();
        for	(int i = 0; i < NUM_OF_TESTS; i++)
        if(isComposite(rand()%RANGE))
        ++counter;
        t2 = clock();
        
        cout << "For " << NUM_OF_TESTS << " pseudo-random values" << endl;
        cout << "within the range 0.." << RANGE << endl;
        cout << ((NUM_OF_TESTS-counter)*100.0)/NUM_OF_TESTS << "% were prime numbers." << endl;
        cout << "\nInitial clock ticks value is " << t1 << endl;
        cout << "Final clock ticks value is " << t2 << endl;
        cout << "Difference in clock ticks is " << difftime(t2,t1) << endl;
        cout << "Clock ticks per second value is " << CLOCKS_PER_SEC << endl;
        cout << "\nActual time of calculations is " << ((t2-t1)/CLOCKS_PER_SEC) << " sec" << endl;
        return 0;
        }
        
        bool isComposite(const int y)
        {
        bool result = false;
        
        for (int i = 2; i <= ceil(y/2.0); i++)
            {	
            if (y%i == 0) result = true;
            }
            return result;
            }
            
            

    Подготовим тест:

  • icl /FePOd /Od Primes.cpp
  • icl /FePx /QaxSSSE3 Primes.cpp
  • Сравнить результаты

    Intel Core 2 Duo T3700 2.00 ГГц, 2 Гб ОЗУ

    Среднее значение времени выполнения:

  • POd . . . . . 29.8 с.
  • Px . . . . . . 4.5 с.
  • Вопрос. Объясните результаты теста.

    Автоматическое распараллеливание

    /Qparallel (Windows), -parallel (Linux)

  • Автоматическое распараллеливание.
  • Определяются те части кода, которые можно распараллелить.
  • Выполняется анализ зависимостей.
  • Разделение данных для параллельной обработки.
  • Работа с циклами.
  • Пример 3

    FermatsCubes.cpp

    #include "time.h"
    #include <iostream>
        #include "stdlib.h"
        #include "conio.h"
        #include "math.h"
        using namespace std;
        const int RANGE = 2000;
        long sumOfCubes(const int x, const int y);
        int main()
        {
        time_t t1, t2;
        bool isDisproven = false;
        cout << "Checking for mispredictions of Fermat's Great Theorem" << endl;
        cout << "for cubes in range 3.." << RANGE << endl;
        t1 = clock();
        for (int i = 3; i < RANGE; i++)
        {
        for(int k = 1; k < i; k++)
        {
        for(int j = 1; j < i; j++)
        {
        if (long(i*i*i) == sumOfCubes(k,j)) 
        {
        cout << k << " " << j << " " << i << endl;
        isDisproven = true;
        }
        }
        }
        }
        t2 = clock();
        cout << "\nAre theorem's predictions correct? +" << !(isDisproven) << endl;
        cout << "\nInitial clock ticks value is " << t1 << endl;
        cout << "Final clock ticks value is " << t2 << endl;
        cout << "Difference in clock ticks is " << difftime(t2,t1) << endl;
        cout << "Clock ticks per second value is " << CLOCKS_PER_SEC << endl;
        cout << "\nActual time of calculations is " << ((t2-t1)/CLOCKS_PER_SEC) << " sec" << endl;
        return 0;
        }
        long sumOfCubes(const int x, const int y)
        {
        return (x*x*x + y*y*y);
        }    
            

    Подготовим тест:

  • icl /FeFCOd /Od FermatsCubes.cpp
  • icl /FeFCPar /Qparallel FermatsCubes.cpp
  • Среднее значение времени выполнения:

  • FCOd . . . . . 25.95 с.
  • FCPar . . . . . 3.78 с.
  • Вопрос. Объясните результаты теста.

    Оптимизация с профилированием

    /Qprof-gen (Windows), -prof-gen (Linux)

    /Qprof-use (Windows), -prof-use (Linux)

  • Инструментовка.
  • Сбор информации.
  • Компиляция с учетом проанализированных данных.
  • Пример 4

    Branches.cpp

    #include "time.h"
    #include <iostream>
        #include "stdlib.h"
        #include "conio.h"
        #include "math.h"
        using namespace std;
        bool slow_func(void);
        bool quick_func(void);
        
        int main()
        {
        time_t t1, t2;
        t1 = clock();
        if(slow_func()  quick_func()) cout << "You can't see this...";
        t2 = clock();
        cout << "\nInitial clock ticks value is " << t1 << endl;
        cout << "Final clock ticks value is " << t2 << endl;
        cout << "Difference in clock ticks is " << difftime(t2,t1) << endl;
        cout << "Clock ticks per second value is " << CLOCKS_PER_SEC << endl;
        cout << "\nActual time of calculations is " << ((t2-t1)/CLOCKS_PER_SEC) << " sec" << endl;
        return 0;
        }
        
        bool slow_func(void)
        {	
        double a(0);
        for(int i = 0; i < 50000000; i++) 
        a = pow((sin(a/2.0)+cos(a/2.0))*(sin(i/2.0)+cos(i/2.0)), 2);
        for(int i = 0; i < 100; i++)
        a += i;
        return !a;
        }
        
        bool quick_func(void)
        {	
        return false;
        }
        
            

    Подготовим тест:

  • icl /FeBOd /Od Branches.cpp
  • icl /FeBPrg /Qprof-gen Branches.cpp
  • BPrg.exe
  • icl /FeBProf /Qprof-use Branches.cpp
  • Сравним результаты.

    Среднее значение времени выполнения:

  • BOd . . . . . 13.171 с.
  • BProf . . . . . 0.0 с.
  • Вопрос. Объясните результаты теста.

    Межпроцедурная оптимизация

    /Qip (Windows), -ip (Linux)

  • Анализ вызываемых функций приложения.
  • Оптимизация многочисленных "маленьких" функций, особенно в циклах.
  • Встраивание (inlining, подстановка кода) – уменьшение накладных расходов, создание возможностей для других видов оптимизации.
  • Удаление неиспользуемого кода.
  • Замена виртуальных вызовов статическими.
  • Замена параметра функции константой.
  • Эффективный анализ зависимостей для оптимизации циклов, векторизации и распараллеливания.
  • Размер бинарного файла и время компиляции увеличиваются.
  • Пример 5

    SquareRoots.cpp

    #include "time.h"
    #include <iostream>
        #include "stdlib.h"
        #include "conio.h"
        #include "math.h"
        using namespace std;
        const int N = 10000; //number of steps in grid used for numerical method
        const int LIM = 50000; //we want to find the sum of square roots of 1..LIM
        double sqrootNewton(double x);
        double returnHalf(double value);
        double multiply(double value1, double value2);
        int main()
        {
        time_t t1, t2;
        double numRes(0), trueRes(0);
        t1 = clock();
        for (int i = 0; i < LIM; i++)
        {
        numRes += sqrootNewton(i+1);
        trueRes += sqrt((i+1)/1.0);
        }
        t2 = clock();
        cout << "Numerical result via Newton's method is " << numRes << endl;
        cout << "PC arithmetical result is " << trueRes << endl;
        cout << "\nInitial clock ticks value is " << t1 << endl;
        cout << "Final clock ticks value is " << t2 << endl;
        cout << "Difference in clock ticks is " << difftime(t2,t1) << endl;
        cout << "Clock ticks per second value is " << CLOCKS_PER_SEC << endl;
        cout << "\nActual time of calculations is " << ((t2-t1)/CLOCKS_PER_SEC) << " sec" << endl;
        _getch();
        return 0;
        }
        double sqrootNewton(double x) // Newtonian method applied for square root
        {
        double sq = 1.0;
        for (int i = 0; i < N; i++)
        sq = returnHalf((sq + multiply(x, 1/sq)));
        return sq;
        }
        double returnHalf(double value)
        {
        return value*0.5;
        }
        
        double multiply(double value1, double value2)
        {
        return value1*value2;
        }    
            

    Подготовим тест:

  • icl /FeSqOd /Od SquareRoots.cpp
  • icl /FeSqip /Qip SquareRoots.cpp
  • Сравнить результаты.

    Среднее значение времени выполнения:

  • SqOd . . . . . 35.69 с.
  • Sqip . . . . . . 9.59 с.
  • Вопрос. Объясните результаты теста.

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