<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
	<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Bulatral</id>
	<title>Алговики - Вклад [ru]</title>
	<link rel="self" type="application/atom+xml" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Bulatral"/>
	<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/ru/%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:%D0%92%D0%BA%D0%BB%D0%B0%D0%B4/Bulatral"/>
	<updated>2026-10-22T17:53:07Z</updated>
	<subtitle>Вклад</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bulatral/QR-%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BF%D0%BB%D0%BE%D1%82%D0%BD%D0%BE%D0%B9_%D0%B2%D0%B5%D1%89%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%BC_%D0%B2%D1%80%D0%B0%D1%89%D0%B5%D0%BD%D0%B8%D0%B9_%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81%D0%B0&amp;diff=28739</id>
		<title>Участник:Bulatral/QR-разложение плотной вещественной матрицы методом вращений Гивенса</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bulatral/QR-%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BF%D0%BB%D0%BE%D1%82%D0%BD%D0%BE%D0%B9_%D0%B2%D0%B5%D1%89%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%BC_%D0%B2%D1%80%D0%B0%D1%89%D0%B5%D0%BD%D0%B8%D0%B9_%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81%D0%B0&amp;diff=28739"/>
		<updated>2021-12-07T13:06:36Z</updated>

		<summary type="html">&lt;p&gt;Bulatral: /* Выводы */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = QR-разложения квадратной матрицы методом вращений Гивенса &lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;2n^3&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;11n-16&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;n^2&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;n^2&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
Автор: [[Участник:Bulatral|Б.И.Валиахметов]]&lt;br /&gt;
&lt;br /&gt;
Необходимо построить QR-разложение квадратной вещественной &amp;lt;math&amp;gt;n \times n&amp;lt;/math&amp;gt; матрицы &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; методом вращений Гивенса.&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Математическое описание алгоритма и его последовательная версия приведены на портале AlgoWiki: [[Метод Гивенса (вращений) QR-разложения квадратной матрицы (вещественный точечный вариант)]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Программная реализация ==&lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
Отметим, что по сути, после применения вращений к исходной матрице мы получаем матрицу &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; и для получения матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; необходимо выполнить обратные вращения над единичной матрицей.&lt;br /&gt;
Однако на практике это не нужно, так как в большинстве случаев требуется умножать на &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;Q^*&amp;lt;/math&amp;gt;, а для этого достаточно применить подсчитанные ранее вращения.&lt;br /&gt;
Также этот подход дает меньшую численную ошибку и выполняется быстрее.&lt;br /&gt;
&lt;br /&gt;
Вычислительным ядром исследуемого метода являются вращения строк исходной матрицы.&lt;br /&gt;
Нами был выбран блочный подход к реализации метода, позволяющий теоретически достичь линейной сложности алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Для удобства будем полагать, что всевозможные размеры и параметры таковы, что все распределения производятся нацело (например, степени 2).&lt;br /&gt;
&lt;br /&gt;
Зададим размер блока &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; и размер подблока &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt;. &lt;br /&gt;
Пусть нам доступны &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; процессов по &amp;lt;math&amp;gt;t&amp;lt;/math&amp;gt; нити в каждом.&lt;br /&gt;
Разобьем матрицу &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; на блоки размера &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; и распределим блочные столбцы по процессам так: столбец с номером &amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt; хранится на процессе с номером &amp;lt;math&amp;gt;j \mod p&amp;lt;/math&amp;gt;, чтобы распределение количества вращений было более равномерным и узлы меньше простаивали в ожидании пересылок.&lt;br /&gt;
Также это способствует активному использованию кэш-памяти.&lt;br /&gt;
Такое распределение легко произвести, если матрица задается как функция вычисления ее элемента, иначе необходимо провести некоторые пересылки (их мы не учитываем при подсчете времени работы алгоритма).&lt;br /&gt;
На каждом процессе блоки по блочным строкам (а сами блоки внутри - по стодбцам), так они задействуются совместно на шагах алгоритма.&lt;br /&gt;
&lt;br /&gt;
Далее будем по очереди обнулять поддиагонали диагональных блоков и блоки ниже диагональных.&lt;br /&gt;
Процесс, на котором хранится блок (ведущий), сначала вычисляет необходимые вращения для данного блока (процедура DLARTG библиотеки LAPACK), а затем рассылает их по остальным процессам (процедурой MPI_Ibcast стандарта MPI).&lt;br /&gt;
Во время неблокирующей пересылки ведущий процесс производит вращения на оставшиеся блоки в блочной строке и приступает к вычислению вращений для последующих блоков в столбце.&lt;br /&gt;
&lt;br /&gt;
Как проводятся вращения на блоке, который сейчас не обнуляется?&lt;br /&gt;
Каждый процесс (в том числе и ведущий), получив набор вращений, применяет их в той же блочной строке, что и процесс-отправитель.&lt;br /&gt;
Каждый блок дополнительно разбивается на блочные подстолбцы ширины &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; и данные подблоки статически распределяются по нитям на процессе (директива omp parallel for библиотеки OpenMP), которые и производят вращения (процедура DROT библиотеки BLAS).&lt;br /&gt;
Этим дополнительным разделением мы задействуем дополнительный ресурс параллелизма и, что не менее важно, эффективно используем кэш-память высокого уровня.&lt;br /&gt;
&lt;br /&gt;
==== Реализация алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Реализация алгоритма выполнена на языке C++11 с использованием стандартных библиотек языка, а также пакетов OpenMPI и OpenBLAS.&lt;br /&gt;
Визуализация для результатов исследования параллельных свойств выполнена на языке Python3 с использованием библиотек numpy и matplotlib в среде Jupyter Notebook.&lt;br /&gt;
Полный код программы находится в репозитории по [https://github.com/bulatral42/mpiQR ссылке], здесь приведем реализацию основной процедуры алгоритма.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Гивенс t(p=1,2).png|thumb|right|500px|Рисунок 1. Разница во времени между 1 и 2 процессами.]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Гивенс-3Д,Т=1.png|540px|обрамить|справа|Рисунок 2. График зависимости времени от числа процессов и размера матрицы для T=1 нити]]&lt;br /&gt;
[[Файл:Гивенс-3Д,Т=2.png|540px|обрамить|справа|Рисунок 3. График зависимости времени от числа процессов и размера матрицы для T=2 нитей]]&lt;br /&gt;
[[Файл:Гивенс-3Д,Т=3.png|540px|обрамить|справа|Рисунок 4. График зависимости времени от числа процессов и размера матрицы для T=3 нитей]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;mpi.h&amp;gt;&lt;br /&gt;
#include &amp;lt;omp.h&amp;gt;&lt;br /&gt;
#include &amp;lt;iostream&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;lt;random&amp;gt;&lt;br /&gt;
#include &amp;lt;chrono&amp;gt;&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
#include &amp;quot;BLAS.h&amp;quot;&lt;br /&gt;
#include &amp;quot;LAPACK.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
void block_qrdec(int N, int block_size, int subblock_size, MPI_Comm comm = MPI_COMM_WORLD)&lt;br /&gt;
{&lt;br /&gt;
    double t_start{0}, t_end{0}, mpi_qr_time{0};&lt;br /&gt;
    int n_proc, proc_id, n_thr;&lt;br /&gt;
    MPI_Comm_rank(comm, &amp;amp;proc_id);&lt;br /&gt;
    MPI_Comm_size(comm, &amp;amp;n_proc);&lt;br /&gt;
#pragma omp parallel&lt;br /&gt;
    {&lt;br /&gt;
        n_thr = omp_get_num_threads();&lt;br /&gt;
    }&lt;br /&gt;
    int n_block_cols = N / (n_proc * block_size), n_block_rows = N / block_size; // per proc&lt;br /&gt;
&lt;br /&gt;
    if (proc_id == 0) {&lt;br /&gt;
        std::cout &amp;lt;&amp;lt; &amp;quot;Start mpi-omp-QR, n_proc = &amp;quot; &amp;lt;&amp;lt; n_proc &amp;lt;&amp;lt; &amp;quot;, n_thr = &amp;quot; &amp;lt;&amp;lt; n_thr &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        std::cout &amp;lt;&amp;lt; &amp;quot;N = &amp;quot; &amp;lt;&amp;lt; N &amp;lt;&amp;lt; &amp;quot;, k = &amp;quot; &amp;lt;&amp;lt; block_size &amp;lt;&amp;lt; &amp;quot;, m = &amp;quot; &amp;lt;&amp;lt; subblock_size;&lt;br /&gt;
        std::cout &amp;lt;&amp;lt; &amp;quot;, nbc = &amp;quot; &amp;lt;&amp;lt; n_block_cols &amp;lt;&amp;lt; &amp;quot;, nbr = &amp;quot; &amp;lt;&amp;lt; n_block_rows &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
    }&lt;br /&gt;
    // blocks are stored rowwise, every block inside is stored columnwise&lt;br /&gt;
    double *blocks = new double[n_block_rows * n_block_cols * block_size * block_size];&lt;br /&gt;
&lt;br /&gt;
    std::srand(proc_id);&lt;br /&gt;
    for (int i = 0; i &amp;lt; n_block_rows * n_block_cols * block_size * block_size; ++i) {&lt;br /&gt;
        //blocks[i] = i + proc_id * n_block_rows * n_block_cols * block_size * block_size;&lt;br /&gt;
        blocks[i] = -1.0 + 2 * static_cast&amp;lt;double&amp;gt;(std::rand()) / RAND_MAX;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Main algorithm start&lt;br /&gt;
    t_start = MPI_Wtime();&lt;br /&gt;
&lt;br /&gt;
    int proc_rots_cnt = (n_block_rows - proc_id) * n_block_cols - n_proc * ((n_block_cols - 1) * n_block_cols) / 2;&lt;br /&gt;
    proc_rots_cnt *= block_size * block_size;&lt;br /&gt;
    proc_rots_cnt -= n_block_cols * (block_size * (block_size + 1)) / 2;&lt;br /&gt;
&lt;br /&gt;
    double *my_rotations = new double[2 * proc_rots_cnt];&lt;br /&gt;
    double *out_rotations = new double[2 * block_size * block_size];&lt;br /&gt;
    double *cur_rots = my_rotations;&lt;br /&gt;
&lt;br /&gt;
    // Main cycle&lt;br /&gt;
    for (int b_col = 0; b_col &amp;lt; n_block_cols * n_proc; ++b_col) {&lt;br /&gt;
        int sender_id = b_col % n_proc;&lt;br /&gt;
        int local_b_col = b_col / n_proc;&lt;br /&gt;
        double *main_block = blocks + (block_size * block_size) * (b_col * n_block_cols + local_b_col);&lt;br /&gt;
        for (int b_row = b_col; b_row &amp;lt; n_block_rows; ++b_row) {&lt;br /&gt;
            double *cur_block = blocks + (block_size * block_size) * (b_row * n_block_cols + local_b_col);&lt;br /&gt;
            int n_rots{0};&lt;br /&gt;
            bool triang_zero = b_row == b_col; // Zerorize triangular part or full block&lt;br /&gt;
            if (proc_id == sender_id) { // Sender part&lt;br /&gt;
                for (int j = 0; j &amp;lt; block_size; ++j) {&lt;br /&gt;
                    for (int i = triang_zero ? j + 1 : 0; i &amp;lt; block_size; ++i) {&lt;br /&gt;
                        double r{0.0};&lt;br /&gt;
                        dlartg(main_block[j * block_size + j], cur_block[j * block_size + i],&lt;br /&gt;
                               cur_rots[2 * n_rots], cur_rots[2 * n_rots + 1], r);&lt;br /&gt;
                        drot(block_size - j - 1, main_block + (j + 1) * block_size + j, block_size,&lt;br /&gt;
                             cur_block + (j + 1) * block_size + i, block_size,&lt;br /&gt;
                             cur_rots[2 * n_rots], cur_rots[2 * n_rots + 1]);&lt;br /&gt;
                        main_block[j * block_size + j] = r;&lt;br /&gt;
                        cur_block[j * block_size + i] = 0.0;&lt;br /&gt;
                        ++n_rots;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                if (triang_zero) {&lt;br /&gt;
                    n_rots = ((block_size - 1) * block_size) / 2;&lt;br /&gt;
                } else {&lt;br /&gt;
                    n_rots = block_size * block_size;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            // Send/receive rotations&lt;br /&gt;
            MPI_Request req_bcast;&lt;br /&gt;
            double *senrecv_rots = proc_id == sender_id ? cur_rots : out_rotations;&lt;br /&gt;
            MPI_Ibcast(senrecv_rots, 2 * n_rots, MPI_DOUBLE, sender_id, comm, &amp;amp;req_bcast);&lt;br /&gt;
&lt;br /&gt;
            // Perform rotations to other blocks in row&lt;br /&gt;
            double *up_other_blocks = main_block;&lt;br /&gt;
            double *dn_other_blocks = cur_block;&lt;br /&gt;
            int n_subblocks = (block_size / subblock_size) * (n_block_cols - b_col);&lt;br /&gt;
            if (proc_id == sender_id) { // Current block has already been rotated&lt;br /&gt;
                up_other_blocks += block_size * block_size;&lt;br /&gt;
                dn_other_blocks += block_size * block_size;&lt;br /&gt;
                n_subblocks -= block_size / subblock_size;&lt;br /&gt;
            } else {&lt;br /&gt;
                MPI_Status status;&lt;br /&gt;
                MPI_Wait(&amp;amp;req_bcast, &amp;amp;status);&lt;br /&gt;
            }&lt;br /&gt;
#pragma omp parallel for schedule(static)&lt;br /&gt;
            for (int subbl_id = 0; subbl_id &amp;lt; n_subblocks; ++subbl_id) {&lt;br /&gt;
                double *up_subblock = up_other_blocks + block_size * subblock_size * subbl_id;&lt;br /&gt;
                double *dn_subblock = dn_other_blocks + block_size * subblock_size * subbl_id;&lt;br /&gt;
                int up_off{0};&lt;br /&gt;
                int dn_off = triang_zero ? up_off + 1 : 0;&lt;br /&gt;
                for (int i = 0; i &amp;lt; n_rots; ++i) {&lt;br /&gt;
                    drot(subblock_size, up_subblock + up_off, block_size, dn_subblock + dn_off,&lt;br /&gt;
                         block_size, senrecv_rots[2 * i], senrecv_rots[2 * i + 1]);&lt;br /&gt;
                    ++dn_off;&lt;br /&gt;
                    if (dn_off % block_size == 0) {&lt;br /&gt;
                        ++up_off;&lt;br /&gt;
                        dn_off = triang_zero ? up_off + 1 : 0;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            if (proc_id == sender_id) { // Current block has already been rotated&lt;br /&gt;
                MPI_Status status;&lt;br /&gt;
                MPI_Wait(&amp;amp;req_bcast, &amp;amp;status);&lt;br /&gt;
                cur_rots += 2 * n_rots;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    MPI_Barrier(comm);&lt;br /&gt;
    t_end = MPI_Wtime();&lt;br /&gt;
    mpi_qr_time = t_end - t_start;&lt;br /&gt;
    if (proc_id == 0) {&lt;br /&gt;
        std::cout &amp;lt;&amp;lt; &amp;quot;MPI QR time = &amp;quot; &amp;lt;&amp;lt; mpi_qr_time &amp;lt;&amp;lt; &amp;quot; sec&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    delete[] my_rotations;&lt;br /&gt;
    delete[] out_rotations;&lt;br /&gt;
    delete[] blocks;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Численные эксперименты ====&lt;br /&gt;
&lt;br /&gt;
[[Файл:Гивенс-N=4,T=1.png|обрамить|справа|Рисунок 5. График зависимости времени от числа процессов, &amp;lt;math&amp;gt;N=4096, T=1&amp;lt;/math&amp;gt;]]&lt;br /&gt;
[[Файл:Гивенс-N=5,T=1.png|обрамить|справа|Рисунок 6. График зависимости времени от числа процессов, &amp;lt;math&amp;gt;N=8192, T=1&amp;lt;/math&amp;gt;]]&lt;br /&gt;
[[Файл:Гивенс-N=8192,T=1,2,3.png|обрамить|справа|Рисунок 7. График зависимости времени от количества процессов при разном числе нитей, &amp;lt;math&amp;gt;N=8192&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
Эксперименты по установлению параллельных свойств алгоритма проводились на кластере ИВМ РАН.&lt;br /&gt;
Характеристики узлов кластера на момент написания раздела (полное описание по [https://cluster2.inm.ras.ru/info/nodes/ ссылке]):&lt;br /&gt;
* Compute Node Arbyte Alkazar R2Q50 G5.&lt;br /&gt;
* 40 ядер (два 20-ядерных процессора Intel Xeon Gold 6230@2.10ГГц).&lt;br /&gt;
* Оперативная память: 256/384 Гб.&lt;br /&gt;
* Дисковая память: 480 Гб SSD.&lt;br /&gt;
* Операционная система: SUSE Linux Enterprise Server 15 SP2 (x86\_64).&lt;br /&gt;
&lt;br /&gt;
Были выбраны следующие параметры запусков. Некоторые запуски не проводились ввиду их слишком большой потенциальной длительности или слишком малого необходимого значения размера блока.&lt;br /&gt;
&lt;br /&gt;
1. На предварительных запусках были установлены оптимальные параметры блочного разбиения: &amp;lt;math&amp;gt;k = 128&amp;lt;/math&amp;gt;,  &amp;lt;math&amp;gt;m = 16&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Размеры матриц выбирались в виде степеней 2: &amp;lt;math&amp;gt;N = 2^s, s \in \overline{8, 16}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3. Число процессов выбиралось в виде степеней 2:&amp;lt;math&amp;gt;P = 2^l, l \in \overline{0, 6}&amp;lt;/math&amp;gt;, при условии &amp;lt;math&amp;gt;P \geq \dfrac{N}{k}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
4. Число нитей выбиралось из набора: &amp;lt;math&amp;gt;T \in \{1, 2, 4\}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
5. Для каждой конфигурации параметров проводилось 3 запуска, измеренное время работы усреднялось.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
На Рисунке 1 показано, как ускорение уже на 2 процессах существенно больше чем в 2 раза по сравнению с последовательной программой.&lt;br /&gt;
&lt;br /&gt;
На Рисунках 2, 3, 4 приведены графики зависимости времени от размеров матрицы и количества процессов (для удобства рассмотрения все величины прологарифмированы)&lt;br /&gt;
&lt;br /&gt;
На Рисунках 5, 6 приведены графики зависимости времени работы программы от количества процессов при фиксированном размере матрицы &amp;lt;math&amp;gt;N \in \{4096, 81921\}&amp;lt;/math&amp;gt; и количество потоков &amp;lt;math&amp;gt;T = 1&amp;lt;/math&amp;gt;.&lt;br /&gt;
Видим, что на малом числе процессов ускорение почти линейное, затем достигается некоторая граница и время начинает несколько расти из-за увеличения накладных расходов.&lt;br /&gt;
Также это может быть связано с тем, что процессы располагаются на большем числе отдельных узлов и скорость передачи сообщений между ними меньше, чем внутри одного узла.&lt;br /&gt;
&lt;br /&gt;
На Рисунке 7 приведены графики зависимости времени работы программы от количества процессов при фиксированном размере матрицы &amp;lt;math&amp;gt;N = 8192&amp;lt;/math&amp;gt; и различном количестве потоков &amp;lt;math&amp;gt;T \in \{1, 2, 4\}&amp;lt;/math&amp;gt;.&lt;br /&gt;
Можем заметить, что использование двух потоков дает практически двукратный выигрыш во времени по сравнению с одним, однако 4 потока работают уже дольше.&lt;br /&gt;
Вероятно, это связано с накладными расходами на их синхронизацию и менее равномерным распределением задач.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Выводы ====&lt;br /&gt;
&lt;br /&gt;
В рамках практической работы мы реализовали параллельную версию алгоритма QR-разложения квадратной вещественной матрицы методом вращений Гивенса и изучили масштабируемость написанной программы.&lt;br /&gt;
Из результатов проделанной работы можем сделать следующие выводы:&lt;br /&gt;
* Реализованная нами программа выполняет поставленную задачу.&lt;br /&gt;
* Параллельная программа по сравнению с последовательной версией ускоряется в большее число раз, чем число процессов - это говорит о хороших параллельный свойствах алгоритма.&lt;br /&gt;
* На малом (от 2 до 8) количестве процессов имеет место почти линейное ускорение программы.&lt;br /&gt;
* При большом количестве процессов (&amp;lt;math&amp;gt;\geq 16&amp;lt;/math&amp;gt;) или потоков (4) на исследованных размерах матриц программа заметно не ускоряется, так как накладные расходы и несбалансированность &amp;quot;перевешивают&amp;quot; ускорение.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[En: Givens method]]&lt;/div&gt;</summary>
		<author><name>Bulatral</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bulatral/QR-%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BF%D0%BB%D0%BE%D1%82%D0%BD%D0%BE%D0%B9_%D0%B2%D0%B5%D1%89%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%BC_%D0%B2%D1%80%D0%B0%D1%89%D0%B5%D0%BD%D0%B8%D0%B9_%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81%D0%B0&amp;diff=28738</id>
		<title>Участник:Bulatral/QR-разложение плотной вещественной матрицы методом вращений Гивенса</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bulatral/QR-%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BF%D0%BB%D0%BE%D1%82%D0%BD%D0%BE%D0%B9_%D0%B2%D0%B5%D1%89%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%BC_%D0%B2%D1%80%D0%B0%D1%89%D0%B5%D0%BD%D0%B8%D0%B9_%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81%D0%B0&amp;diff=28738"/>
		<updated>2021-12-07T13:05:13Z</updated>

		<summary type="html">&lt;p&gt;Bulatral: Основная часть раздела 2.4&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = QR-разложения квадратной матрицы методом вращений Гивенса &lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;2n^3&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;11n-16&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;n^2&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;n^2&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
Автор: [[Участник:Bulatral|Б.И.Валиахметов]]&lt;br /&gt;
&lt;br /&gt;
Необходимо построить QR-разложение квадратной вещественной &amp;lt;math&amp;gt;n \times n&amp;lt;/math&amp;gt; матрицы &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; методом вращений Гивенса.&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Математическое описание алгоритма и его последовательная версия приведены на портале AlgoWiki: [[Метод Гивенса (вращений) QR-разложения квадратной матрицы (вещественный точечный вариант)]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Программная реализация ==&lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
Отметим, что по сути, после применения вращений к исходной матрице мы получаем матрицу &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; и для получения матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; необходимо выполнить обратные вращения над единичной матрицей.&lt;br /&gt;
Однако на практике это не нужно, так как в большинстве случаев требуется умножать на &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;Q^*&amp;lt;/math&amp;gt;, а для этого достаточно применить подсчитанные ранее вращения.&lt;br /&gt;
Также этот подход дает меньшую численную ошибку и выполняется быстрее.&lt;br /&gt;
&lt;br /&gt;
Вычислительным ядром исследуемого метода являются вращения строк исходной матрицы.&lt;br /&gt;
Нами был выбран блочный подход к реализации метода, позволяющий теоретически достичь линейной сложности алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Для удобства будем полагать, что всевозможные размеры и параметры таковы, что все распределения производятся нацело (например, степени 2).&lt;br /&gt;
&lt;br /&gt;
Зададим размер блока &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; и размер подблока &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt;. &lt;br /&gt;
Пусть нам доступны &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; процессов по &amp;lt;math&amp;gt;t&amp;lt;/math&amp;gt; нити в каждом.&lt;br /&gt;
Разобьем матрицу &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; на блоки размера &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; и распределим блочные столбцы по процессам так: столбец с номером &amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt; хранится на процессе с номером &amp;lt;math&amp;gt;j \mod p&amp;lt;/math&amp;gt;, чтобы распределение количества вращений было более равномерным и узлы меньше простаивали в ожидании пересылок.&lt;br /&gt;
Также это способствует активному использованию кэш-памяти.&lt;br /&gt;
Такое распределение легко произвести, если матрица задается как функция вычисления ее элемента, иначе необходимо провести некоторые пересылки (их мы не учитываем при подсчете времени работы алгоритма).&lt;br /&gt;
На каждом процессе блоки по блочным строкам (а сами блоки внутри - по стодбцам), так они задействуются совместно на шагах алгоритма.&lt;br /&gt;
&lt;br /&gt;
Далее будем по очереди обнулять поддиагонали диагональных блоков и блоки ниже диагональных.&lt;br /&gt;
Процесс, на котором хранится блок (ведущий), сначала вычисляет необходимые вращения для данного блока (процедура DLARTG библиотеки LAPACK), а затем рассылает их по остальным процессам (процедурой MPI_Ibcast стандарта MPI).&lt;br /&gt;
Во время неблокирующей пересылки ведущий процесс производит вращения на оставшиеся блоки в блочной строке и приступает к вычислению вращений для последующих блоков в столбце.&lt;br /&gt;
&lt;br /&gt;
Как проводятся вращения на блоке, который сейчас не обнуляется?&lt;br /&gt;
Каждый процесс (в том числе и ведущий), получив набор вращений, применяет их в той же блочной строке, что и процесс-отправитель.&lt;br /&gt;
Каждый блок дополнительно разбивается на блочные подстолбцы ширины &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; и данные подблоки статически распределяются по нитям на процессе (директива omp parallel for библиотеки OpenMP), которые и производят вращения (процедура DROT библиотеки BLAS).&lt;br /&gt;
Этим дополнительным разделением мы задействуем дополнительный ресурс параллелизма и, что не менее важно, эффективно используем кэш-память высокого уровня.&lt;br /&gt;
&lt;br /&gt;
==== Реализация алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Реализация алгоритма выполнена на языке C++11 с использованием стандартных библиотек языка, а также пакетов OpenMPI и OpenBLAS.&lt;br /&gt;
Визуализация для результатов исследования параллельных свойств выполнена на языке Python3 с использованием библиотек numpy и matplotlib в среде Jupyter Notebook.&lt;br /&gt;
Полный код программы находится в репозитории по [https://github.com/bulatral42/mpiQR ссылке], здесь приведем реализацию основной процедуры алгоритма.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Гивенс t(p=1,2).png|thumb|right|500px|Рисунок 1. Разница во времени между 1 и 2 процессами.]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Гивенс-3Д,Т=1.png|540px|обрамить|справа|Рисунок 2. График зависимости времени от числа процессов и размера матрицы для T=1 нити]]&lt;br /&gt;
[[Файл:Гивенс-3Д,Т=2.png|540px|обрамить|справа|Рисунок 3. График зависимости времени от числа процессов и размера матрицы для T=2 нитей]]&lt;br /&gt;
[[Файл:Гивенс-3Д,Т=3.png|540px|обрамить|справа|Рисунок 4. График зависимости времени от числа процессов и размера матрицы для T=3 нитей]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;mpi.h&amp;gt;&lt;br /&gt;
#include &amp;lt;omp.h&amp;gt;&lt;br /&gt;
#include &amp;lt;iostream&amp;gt;&lt;br /&gt;
#include &amp;lt;cmath&amp;gt;&lt;br /&gt;
#include &amp;lt;random&amp;gt;&lt;br /&gt;
#include &amp;lt;chrono&amp;gt;&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
#include &amp;quot;BLAS.h&amp;quot;&lt;br /&gt;
#include &amp;quot;LAPACK.h&amp;quot;&lt;br /&gt;
&lt;br /&gt;
void block_qrdec(int N, int block_size, int subblock_size, MPI_Comm comm = MPI_COMM_WORLD)&lt;br /&gt;
{&lt;br /&gt;
    double t_start{0}, t_end{0}, mpi_qr_time{0};&lt;br /&gt;
    int n_proc, proc_id, n_thr;&lt;br /&gt;
    MPI_Comm_rank(comm, &amp;amp;proc_id);&lt;br /&gt;
    MPI_Comm_size(comm, &amp;amp;n_proc);&lt;br /&gt;
#pragma omp parallel&lt;br /&gt;
    {&lt;br /&gt;
        n_thr = omp_get_num_threads();&lt;br /&gt;
    }&lt;br /&gt;
    int n_block_cols = N / (n_proc * block_size), n_block_rows = N / block_size; // per proc&lt;br /&gt;
&lt;br /&gt;
    if (proc_id == 0) {&lt;br /&gt;
        std::cout &amp;lt;&amp;lt; &amp;quot;Start mpi-omp-QR, n_proc = &amp;quot; &amp;lt;&amp;lt; n_proc &amp;lt;&amp;lt; &amp;quot;, n_thr = &amp;quot; &amp;lt;&amp;lt; n_thr &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        std::cout &amp;lt;&amp;lt; &amp;quot;N = &amp;quot; &amp;lt;&amp;lt; N &amp;lt;&amp;lt; &amp;quot;, k = &amp;quot; &amp;lt;&amp;lt; block_size &amp;lt;&amp;lt; &amp;quot;, m = &amp;quot; &amp;lt;&amp;lt; subblock_size;&lt;br /&gt;
        std::cout &amp;lt;&amp;lt; &amp;quot;, nbc = &amp;quot; &amp;lt;&amp;lt; n_block_cols &amp;lt;&amp;lt; &amp;quot;, nbr = &amp;quot; &amp;lt;&amp;lt; n_block_rows &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
    }&lt;br /&gt;
    // blocks are stored rowwise, every block inside is stored columnwise&lt;br /&gt;
    double *blocks = new double[n_block_rows * n_block_cols * block_size * block_size];&lt;br /&gt;
&lt;br /&gt;
    std::srand(proc_id);&lt;br /&gt;
    for (int i = 0; i &amp;lt; n_block_rows * n_block_cols * block_size * block_size; ++i) {&lt;br /&gt;
        //blocks[i] = i + proc_id * n_block_rows * n_block_cols * block_size * block_size;&lt;br /&gt;
        blocks[i] = -1.0 + 2 * static_cast&amp;lt;double&amp;gt;(std::rand()) / RAND_MAX;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Main algorithm start&lt;br /&gt;
    t_start = MPI_Wtime();&lt;br /&gt;
&lt;br /&gt;
    int proc_rots_cnt = (n_block_rows - proc_id) * n_block_cols - n_proc * ((n_block_cols - 1) * n_block_cols) / 2;&lt;br /&gt;
    proc_rots_cnt *= block_size * block_size;&lt;br /&gt;
    proc_rots_cnt -= n_block_cols * (block_size * (block_size + 1)) / 2;&lt;br /&gt;
&lt;br /&gt;
    double *my_rotations = new double[2 * proc_rots_cnt];&lt;br /&gt;
    double *out_rotations = new double[2 * block_size * block_size];&lt;br /&gt;
    double *cur_rots = my_rotations;&lt;br /&gt;
&lt;br /&gt;
    // Main cycle&lt;br /&gt;
    for (int b_col = 0; b_col &amp;lt; n_block_cols * n_proc; ++b_col) {&lt;br /&gt;
        int sender_id = b_col % n_proc;&lt;br /&gt;
        int local_b_col = b_col / n_proc;&lt;br /&gt;
        double *main_block = blocks + (block_size * block_size) * (b_col * n_block_cols + local_b_col);&lt;br /&gt;
        for (int b_row = b_col; b_row &amp;lt; n_block_rows; ++b_row) {&lt;br /&gt;
            double *cur_block = blocks + (block_size * block_size) * (b_row * n_block_cols + local_b_col);&lt;br /&gt;
            int n_rots{0};&lt;br /&gt;
            bool triang_zero = b_row == b_col; // Zerorize triangular part or full block&lt;br /&gt;
            if (proc_id == sender_id) { // Sender part&lt;br /&gt;
                for (int j = 0; j &amp;lt; block_size; ++j) {&lt;br /&gt;
                    for (int i = triang_zero ? j + 1 : 0; i &amp;lt; block_size; ++i) {&lt;br /&gt;
                        double r{0.0};&lt;br /&gt;
                        dlartg(main_block[j * block_size + j], cur_block[j * block_size + i],&lt;br /&gt;
                               cur_rots[2 * n_rots], cur_rots[2 * n_rots + 1], r);&lt;br /&gt;
                        drot(block_size - j - 1, main_block + (j + 1) * block_size + j, block_size,&lt;br /&gt;
                             cur_block + (j + 1) * block_size + i, block_size,&lt;br /&gt;
                             cur_rots[2 * n_rots], cur_rots[2 * n_rots + 1]);&lt;br /&gt;
                        main_block[j * block_size + j] = r;&lt;br /&gt;
                        cur_block[j * block_size + i] = 0.0;&lt;br /&gt;
                        ++n_rots;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                if (triang_zero) {&lt;br /&gt;
                    n_rots = ((block_size - 1) * block_size) / 2;&lt;br /&gt;
                } else {&lt;br /&gt;
                    n_rots = block_size * block_size;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            // Send/receive rotations&lt;br /&gt;
            MPI_Request req_bcast;&lt;br /&gt;
            double *senrecv_rots = proc_id == sender_id ? cur_rots : out_rotations;&lt;br /&gt;
            MPI_Ibcast(senrecv_rots, 2 * n_rots, MPI_DOUBLE, sender_id, comm, &amp;amp;req_bcast);&lt;br /&gt;
&lt;br /&gt;
            // Perform rotations to other blocks in row&lt;br /&gt;
            double *up_other_blocks = main_block;&lt;br /&gt;
            double *dn_other_blocks = cur_block;&lt;br /&gt;
            int n_subblocks = (block_size / subblock_size) * (n_block_cols - b_col);&lt;br /&gt;
            if (proc_id == sender_id) { // Current block has already been rotated&lt;br /&gt;
                up_other_blocks += block_size * block_size;&lt;br /&gt;
                dn_other_blocks += block_size * block_size;&lt;br /&gt;
                n_subblocks -= block_size / subblock_size;&lt;br /&gt;
            } else {&lt;br /&gt;
                MPI_Status status;&lt;br /&gt;
                MPI_Wait(&amp;amp;req_bcast, &amp;amp;status);&lt;br /&gt;
            }&lt;br /&gt;
#pragma omp parallel for schedule(static)&lt;br /&gt;
            for (int subbl_id = 0; subbl_id &amp;lt; n_subblocks; ++subbl_id) {&lt;br /&gt;
                double *up_subblock = up_other_blocks + block_size * subblock_size * subbl_id;&lt;br /&gt;
                double *dn_subblock = dn_other_blocks + block_size * subblock_size * subbl_id;&lt;br /&gt;
                int up_off{0};&lt;br /&gt;
                int dn_off = triang_zero ? up_off + 1 : 0;&lt;br /&gt;
                for (int i = 0; i &amp;lt; n_rots; ++i) {&lt;br /&gt;
                    drot(subblock_size, up_subblock + up_off, block_size, dn_subblock + dn_off,&lt;br /&gt;
                         block_size, senrecv_rots[2 * i], senrecv_rots[2 * i + 1]);&lt;br /&gt;
                    ++dn_off;&lt;br /&gt;
                    if (dn_off % block_size == 0) {&lt;br /&gt;
                        ++up_off;&lt;br /&gt;
                        dn_off = triang_zero ? up_off + 1 : 0;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            if (proc_id == sender_id) { // Current block has already been rotated&lt;br /&gt;
                MPI_Status status;&lt;br /&gt;
                MPI_Wait(&amp;amp;req_bcast, &amp;amp;status);&lt;br /&gt;
                cur_rots += 2 * n_rots;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    MPI_Barrier(comm);&lt;br /&gt;
    t_end = MPI_Wtime();&lt;br /&gt;
    mpi_qr_time = t_end - t_start;&lt;br /&gt;
    if (proc_id == 0) {&lt;br /&gt;
        std::cout &amp;lt;&amp;lt; &amp;quot;MPI QR time = &amp;quot; &amp;lt;&amp;lt; mpi_qr_time &amp;lt;&amp;lt; &amp;quot; sec&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    delete[] my_rotations;&lt;br /&gt;
    delete[] out_rotations;&lt;br /&gt;
    delete[] blocks;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Численные эксперименты ====&lt;br /&gt;
&lt;br /&gt;
[[Файл:Гивенс-N=4,T=1.png|обрамить|справа|Рисунок 5. График зависимости времени от числа процессов, &amp;lt;math&amp;gt;N=4096, T=1&amp;lt;/math&amp;gt;]]&lt;br /&gt;
[[Файл:Гивенс-N=5,T=1.png|обрамить|справа|Рисунок 6. График зависимости времени от числа процессов, &amp;lt;math&amp;gt;N=8192, T=1&amp;lt;/math&amp;gt;]]&lt;br /&gt;
[[Файл:Гивенс-N=8192,T=1,2,3.png|обрамить|справа|Рисунок 7. График зависимости времени от количества процессов при разном числе нитей, &amp;lt;math&amp;gt;N=8192&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
Эксперименты по установлению параллельных свойств алгоритма проводились на кластере ИВМ РАН.&lt;br /&gt;
Характеристики узлов кластера на момент написания раздела (полное описание по [https://cluster2.inm.ras.ru/info/nodes/ ссылке]):&lt;br /&gt;
* Compute Node Arbyte Alkazar R2Q50 G5.&lt;br /&gt;
* 40 ядер (два 20-ядерных процессора Intel Xeon Gold 6230@2.10ГГц).&lt;br /&gt;
* Оперативная память: 256/384 Гб.&lt;br /&gt;
* Дисковая память: 480 Гб SSD.&lt;br /&gt;
* Операционная система: SUSE Linux Enterprise Server 15 SP2 (x86\_64).&lt;br /&gt;
&lt;br /&gt;
Были выбраны следующие параметры запусков. Некоторые запуски не проводились ввиду их слишком большой потенциальной длительности или слишком малого необходимого значения размера блока.&lt;br /&gt;
&lt;br /&gt;
1. На предварительных запусках были установлены оптимальные параметры блочного разбиения: &amp;lt;math&amp;gt;k = 128&amp;lt;/math&amp;gt;,  &amp;lt;math&amp;gt;m = 16&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Размеры матриц выбирались в виде степеней 2: &amp;lt;math&amp;gt;N = 2^s, s \in \overline{8, 16}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3. Число процессов выбиралось в виде степеней 2:&amp;lt;math&amp;gt;P = 2^l, l \in \overline{0, 6}&amp;lt;/math&amp;gt;, при условии &amp;lt;math&amp;gt;P \geq \dfrac{N}{k}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
4. Число нитей выбиралось из набора: &amp;lt;math&amp;gt;T \in \{1, 2, 4\}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
5. Для каждой конфигурации параметров проводилось 3 запуска, измеренное время работы усреднялось.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
На Рисунке 1 показано, как ускорение уже на 2 процессах существенно больше чем в 2 раза по сравнению с последовательной программой.&lt;br /&gt;
&lt;br /&gt;
На Рисунках 2, 3, 4 приведены графики зависимости времени от размеров матрицы и количества процессов (для удобства рассмотрения все величины прологарифмированы)&lt;br /&gt;
&lt;br /&gt;
На Рисунках 5, 6 приведены графики зависимости времени работы программы от количества процессов при фиксированном размере матрицы &amp;lt;math&amp;gt;N \in \{4096, 81921\}&amp;lt;/math&amp;gt; и количество потоков &amp;lt;math&amp;gt;T = 1&amp;lt;/math&amp;gt;.&lt;br /&gt;
Видим, что на малом числе процессов ускорение почти линейное, затем достигается некоторая граница и время начинает несколько расти из-за увеличения накладных расходов.&lt;br /&gt;
Также это может быть связано с тем, что процессы располагаются на большем числе отдельных узлов и скорость передачи сообщений между ними меньше, чем внутри одного узла.&lt;br /&gt;
&lt;br /&gt;
На Рисунке 7 приведены графики зависимости времени работы программы от количества процессов при фиксированном размере матрицы &amp;lt;math&amp;gt;N = 8192&amp;lt;/math&amp;gt; и различном количестве потоков &amp;lt;math&amp;gt;T \in \{1, 2, 4\}&amp;lt;/math&amp;gt;.&lt;br /&gt;
Можем заметить, что использование двух потоков дает практически двукратный выигрыш во времени по сравнению с одним, однако 4 потока работают уже дольше.&lt;br /&gt;
Вероятно, это связано с накладными расходами на их синхронизацию и менее равномерным распределением задач.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Выводы ====&lt;br /&gt;
&lt;br /&gt;
В рамках практической работы мы реализовали параллельную версию алгоритма QR-разложения квадратной вещественной матрицы методом вращений Гивенса и изучили масштабируемость написанной программы.&lt;br /&gt;
Из результатов проделанной работы можем сделать следующие выводы:&lt;br /&gt;
* Реализованная нами программа выполняет поставленную задачу.&lt;br /&gt;
* Параллельная программа по сравнению с последовательной версией ускоряется в большее число раз, чем число процессов - это говорит о хороших параллельный свойствах алгоритма.&lt;br /&gt;
* На малом (от 2 до 8) количестве процессов имеет место почти линейное ускорение программы.&lt;br /&gt;
* При большом количестве процессов ($\geq 16$) или потоков (4) на исследованных размерах матриц программа заметно не ускоряется, так как накладные расходы и несбалансированность &amp;quot;перевешивают&amp;quot; ускорение.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[En: Givens method]]&lt;/div&gt;</summary>
		<author><name>Bulatral</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81-N%3D8192,T%3D1,2,3.png&amp;diff=28737</id>
		<title>Файл:Гивенс-N=8192,T=1,2,3.png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81-N%3D8192,T%3D1,2,3.png&amp;diff=28737"/>
		<updated>2021-12-07T12:55:34Z</updated>

		<summary type="html">&lt;p&gt;Bulatral: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;График зависимости времени от количества процессов при разном числе нитей&lt;/div&gt;</summary>
		<author><name>Bulatral</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81-N%3D5,T%3D1.png&amp;diff=28736</id>
		<title>Файл:Гивенс-N=5,T=1.png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81-N%3D5,T%3D1.png&amp;diff=28736"/>
		<updated>2021-12-07T12:45:09Z</updated>

		<summary type="html">&lt;p&gt;Bulatral: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;График зависимости времени от размера матрицы&lt;/div&gt;</summary>
		<author><name>Bulatral</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81-N%3D4,T%3D1.png&amp;diff=28735</id>
		<title>Файл:Гивенс-N=4,T=1.png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81-N%3D4,T%3D1.png&amp;diff=28735"/>
		<updated>2021-12-07T12:43:48Z</updated>

		<summary type="html">&lt;p&gt;Bulatral: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;График зависимости времени от размера матрицы&lt;/div&gt;</summary>
		<author><name>Bulatral</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81-3%D0%94,T%3D1,2,3.png&amp;diff=28734</id>
		<title>Файл:Гивенс-3Д,T=1,2,3.png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81-3%D0%94,T%3D1,2,3.png&amp;diff=28734"/>
		<updated>2021-12-07T12:21:31Z</updated>

		<summary type="html">&lt;p&gt;Bulatral: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;График зависимости времени от числа процессов и размера матрицы для T=1,2,3 нитей&lt;/div&gt;</summary>
		<author><name>Bulatral</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81-3%D0%94,%D0%A2%3D3.png&amp;diff=28733</id>
		<title>Файл:Гивенс-3Д,Т=3.png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81-3%D0%94,%D0%A2%3D3.png&amp;diff=28733"/>
		<updated>2021-12-07T12:11:43Z</updated>

		<summary type="html">&lt;p&gt;Bulatral: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;График зависимости времени от числа процессов и размера матрицы для T=3 нитей&lt;/div&gt;</summary>
		<author><name>Bulatral</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81-3%D0%94,%D0%A2%3D2.png&amp;diff=28732</id>
		<title>Файл:Гивенс-3Д,Т=2.png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81-3%D0%94,%D0%A2%3D2.png&amp;diff=28732"/>
		<updated>2021-12-07T12:09:25Z</updated>

		<summary type="html">&lt;p&gt;Bulatral: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;График зависимости времени от числа процессов и размера матрицы для 2 нитей&lt;/div&gt;</summary>
		<author><name>Bulatral</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81-3%D0%94,%D0%A2%3D1.png&amp;diff=28731</id>
		<title>Файл:Гивенс-3Д,Т=1.png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81-3%D0%94,%D0%A2%3D1.png&amp;diff=28731"/>
		<updated>2021-12-07T12:02:38Z</updated>

		<summary type="html">&lt;p&gt;Bulatral: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;3Д график зависимости времени от числа процессов и размера матрицы для 1 нити&lt;/div&gt;</summary>
		<author><name>Bulatral</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81_t(p%3D1,2).png&amp;diff=28730</id>
		<title>Файл:Гивенс t(p=1,2).png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81_t(p%3D1,2).png&amp;diff=28730"/>
		<updated>2021-12-07T11:23:47Z</updated>

		<summary type="html">&lt;p&gt;Bulatral: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;зависимость времени от размера матрицы при 1 и 2 процессах&lt;/div&gt;</summary>
		<author><name>Bulatral</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bulatral/QR-%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BF%D0%BB%D0%BE%D1%82%D0%BD%D0%BE%D0%B9_%D0%B2%D0%B5%D1%89%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%BC_%D0%B2%D1%80%D0%B0%D1%89%D0%B5%D0%BD%D0%B8%D0%B9_%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81%D0%B0&amp;diff=28729</id>
		<title>Участник:Bulatral/QR-разложение плотной вещественной матрицы методом вращений Гивенса</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bulatral/QR-%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BF%D0%BB%D0%BE%D1%82%D0%BD%D0%BE%D0%B9_%D0%B2%D0%B5%D1%89%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%BC_%D0%B2%D1%80%D0%B0%D1%89%D0%B5%D0%BD%D0%B8%D0%B9_%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81%D0%B0&amp;diff=28729"/>
		<updated>2021-12-07T01:14:40Z</updated>

		<summary type="html">&lt;p&gt;Bulatral: Новая страница: «{{algorithm | name              = QR-разложения квадратной матрицы методом вращений Гивенса  | serial_complexity...»&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = QR-разложения квадратной матрицы методом вращений Гивенса &lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;2n^3&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;11n-16&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;n^2&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;n^2&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
Автор: [[Участник:Bulatral|Б.И.Валиахметов]]&lt;br /&gt;
&lt;br /&gt;
Необходимо построить QR-разложение квадратной вещественной &amp;lt;math&amp;gt;n \times n&amp;lt;/math&amp;gt; матрицы &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; методом вращений Гивенса.&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Математическое описание алгоритма и его последовательная версия приведены на портале AlgoWiki: [https://algowiki-project.org/ru/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%93%D0%B8%D0%B2%D0%B5%D0%BD%D1%81%D0%B0_(%D0%B2%D1%80%D0%B0%D1%89%D0%B5%D0%BD%D0%B8%D0%B9)_QR-%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_(%D0%B2%D0%B5%D1%89%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9_%D1%82%D0%BE%D1%87%D0%B5%D1%87%D0%BD%D1%8B%D0%B9_%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82) Метод Гивенса (вращений) QR-разложения квадратной матрицы (вещественный точечный вариант) ]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Программная реализация ==&lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
Отметим, что по сути, после применения вращений к исходной матрице мы получаем матрицу &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; и для получения матрицы &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; необходимо выполнить обратные вращения над единичной матрицей.&lt;br /&gt;
Однако на практике это не нужно, так как в большинстве случаев требуется умножать на &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;Q^*&amp;lt;/math&amp;gt;, а для этого достаточно применить подсчитанные ранее вращения.&lt;br /&gt;
Также этот подход дает меньшую численную ошибку и выполняется быстрее.&lt;br /&gt;
&lt;br /&gt;
Вычислительным ядром исследуемого метода являются вращения строк исходной матрицы.&lt;br /&gt;
Нами был выбран блочный подход к реализации метода, возволяющий теоретически достичь линейной сложности алгоритма.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Для удобства будем полагать, что всевозможные размеры и параметры таковы, что все распределения производятся нацело (например, степени 2).&lt;br /&gt;
&lt;br /&gt;
Зададим размер блока &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; и размер подблока &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt;. &lt;br /&gt;
Пусть нам доступны &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; процессов по &amp;lt;math&amp;gt;t&amp;lt;/math&amp;gt; нити в каждом.&lt;br /&gt;
Разобьем матрицу &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; на блоки размера &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; и распределим блочные столбцы по процессам так: столбец с номером &amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt; хранится на процессе с номером &amp;lt;math&amp;gt;j \mod p&amp;lt;/math&amp;gt;, чтобы распределение количества вращений было более равномерным и узлы меньше простаивали в ожидании пересылок.&lt;br /&gt;
Также это способствует активному использованию кэш-памяти.&lt;br /&gt;
Такое распределение легко произвести, если матрица задается как функция вычисления ее элемента, иначе необходимо провести некоторые пересылки (их мы не учитываем при подсчете времени работы алгоритма).&lt;br /&gt;
На каждом процессе блоки по блочным строкам (а сами блоки внутри - по стодбцам), так они задействуются совместно на шагах алгоритма.&lt;br /&gt;
&lt;br /&gt;
Далее будем по очереди обнулять поддиагонали диагональных блоков и блоки ниже диагональных.&lt;br /&gt;
Процесс, на котором хранится блок (ведущий), сначала вычисляет необходимые вращения для данного блока (процедура DLARTG библиотеки LAPACK), а затем рассылает их по остальным процессам (процедурой MPI\_IBcast стандарта MPI).&lt;br /&gt;
Во время неблокирующей пересылки ведущий процесс производит вращения на оставшиеся блоки в блочной строке и приступает к вычислению вращений для последующих блоков в столбце.&lt;br /&gt;
&lt;br /&gt;
Как проводятся вращения на блоке, который сейчас не обнуляется?&lt;br /&gt;
Каждый процесс (в том числе и ведущий), получив набор вращений, применяет их в той же блочной строке, что и процесс-отправитель.&lt;br /&gt;
Каждый блок дополнительно разбивается на блочные подстолбцы ширины &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; и данные подблоки статически распределяются по нитям на процессе (директива omp parallel for библиотеки OpenMP), которые и производят вращения (процедура DROT библиотеки BLAS).&lt;br /&gt;
Этим дополнительным разделением мы задействуем дополнительный ресурс параллелизма и, что не менее важно, эффективно используем кэш-память высокого уровня.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Реализация алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Реализация алгоритма выполнена на языке C++11 с использованием стандартных библиотек языка, а также пакетов OpenMPI и OpenBLAS.&lt;br /&gt;
Визуализация для результатов исследования параллельных свойств выполнена на языке Python3 с использованием библиотек numpy и matplotlib в среде Jupyter Notebook.&lt;br /&gt;
Код программы находится в репозитории по [https://github.com/bulatral42/mpiQR ссылке].&lt;br /&gt;
&lt;br /&gt;
==== Численные эксперименты ====&lt;br /&gt;
&lt;br /&gt;
Эксперименты по установлению параллельных свойств алгоритма проводились на кластере ИВМ РАН.&lt;br /&gt;
Характеристики узлов кластера на момент написания раздела (полное описание по [https://cluster2.inm.ras.ru/info/nodes/ ссылке]):&lt;br /&gt;
    Compute Node Arbyte Alkazar R2Q50 G5.&lt;br /&gt;
    40 ядер (два 20-ядерных процессора Intel Xeon Gold 6230@2.10ГГц).&lt;br /&gt;
    Оперативная память: 256/384 Гб.&lt;br /&gt;
    Дисковая память: 480 Гб SSD.&lt;br /&gt;
    Операционная система: SUSE Linux Enterprise Server 15 SP2 (x86\_64).&lt;br /&gt;
&lt;br /&gt;
Были выбраны следующие параметры запусков.&lt;br /&gt;
    На предварительных запусках были установлены оптимальные параметры блочного разбиения: $k = 128$, $m = 16$.&lt;br /&gt;
    Размеры матриц выбирались в виде степеней 2: $N = 2^s, s \in \overline{8, 16}$.&lt;br /&gt;
    Число процессов выбиралось в виде степеней 2: $P = 2^l, l \in \overline{0, 6}$, при условии $P \geq \dfrac{N}{k}$.&lt;br /&gt;
    Число нитей выбиралось из набора: $T \in \{1, 2, 4\}$.&lt;br /&gt;
    Для каждой конфигурации параметров проводилось 3 запуска, измеренное время работы усреднялось.&lt;br /&gt;
&lt;br /&gt;
В таблицах \ref{tab:1thr}, \ref{tab:2thr}, \ref{tab:3thr} приведены результаты времени работы программы в зависимости от параметров.&lt;br /&gt;
Некоторые запуски не проводились ввиду их слишком большой потенциальной длительности или слишком малого необходимого значения размера блока.&lt;br /&gt;
&lt;br /&gt;
\begin{table}[h]&lt;br /&gt;
    \centering&lt;br /&gt;
    \begin{tabular}{|c|c|c|c|c|c|c|c|c|c|}&lt;br /&gt;
    \hline&lt;br /&gt;
        P \textbackslash N &amp;amp; 256 &amp;amp; 512 &amp;amp; 1024 &amp;amp; 2048 &amp;amp; 4096 &amp;amp; 8192 &amp;amp; 16384 &amp;amp; 32768 &amp;amp; 65536  \\ \hline&lt;br /&gt;
        1 &amp;amp; 0.0402 &amp;amp; 0.3432 &amp;amp; 2.824 &amp;amp; 29.84 &amp;amp; 498.4 &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A \\ \hline&lt;br /&gt;
        2 &amp;amp; N/A &amp;amp; 0.0459 &amp;amp; 0.2231 &amp;amp; 1.372 &amp;amp; 9.505 &amp;amp; 99.99 &amp;amp; 807.177 &amp;amp; N/A &amp;amp; N/A \\ \hline&lt;br /&gt;
        4 &amp;amp; N/A &amp;amp; 0.03902 &amp;amp; 0.1529 &amp;amp; 0.7388 &amp;amp; 4.615 &amp;amp; 34.43 &amp;amp; 250.7 &amp;amp; 1555.2 &amp;amp; N/A \\ \hline&lt;br /&gt;
        8 &amp;amp; N/A &amp;amp; N/A &amp;amp; 0.3030 &amp;amp; 0.6821 &amp;amp; 3.046 &amp;amp; 15.33 &amp;amp; 89.11 &amp;amp; 588.2 &amp;amp; N/A \\ \hline&lt;br /&gt;
        16 &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A &amp;amp; 0.6065 &amp;amp; 2.502 &amp;amp; 10.68 &amp;amp; 49.22 &amp;amp; 254.7 &amp;amp; N/A \\ \hline&lt;br /&gt;
        32 &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A &amp;amp; 2.445 &amp;amp; 9.951 &amp;amp; 41.64 &amp;amp; 181.14 &amp;amp; N/A \\ \hline&lt;br /&gt;
        64 &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A &amp;amp; 10.24 &amp;amp; 39.56 &amp;amp; 161.6 &amp;amp; 675.9 \\ \hline&lt;br /&gt;
    \end{tabular}&lt;br /&gt;
    \caption{Время работы в сек., $T = 1$ нить}&lt;br /&gt;
    \label{tab:1thr}&lt;br /&gt;
\end{table}&lt;br /&gt;
\begin{table}[h]&lt;br /&gt;
    \centering&lt;br /&gt;
    \begin{tabular}{|c|c|c|c|c|c|c|c|c|c|}&lt;br /&gt;
    \hline&lt;br /&gt;
        P \textbackslash N &amp;amp; 256 &amp;amp; 512 &amp;amp; 1024 &amp;amp; 2048 &amp;amp; 4096 &amp;amp; 8192 &amp;amp; 16384 &amp;amp; 32768 &amp;amp; 65536  \\ \hline&lt;br /&gt;
        2 &amp;amp; N/A &amp;amp; 0.08189 &amp;amp; 0.2260 &amp;amp; 1.274 &amp;amp; 8.117 &amp;amp; 56.26 &amp;amp; 416.2 &amp;amp; N/A &amp;amp; N/A \\ \hline&lt;br /&gt;
        4 &amp;amp; N/A &amp;amp; 0.06885 &amp;amp; 0.1688 &amp;amp; 0.7482 &amp;amp; 3.926 &amp;amp; 22.35 &amp;amp; 146.8 &amp;amp; 990.4 &amp;amp; N/A \\ \hline&lt;br /&gt;
        8 &amp;amp; N/A &amp;amp; N/A &amp;amp; 0.1630 &amp;amp; 0.6467 &amp;amp; 2.744 &amp;amp; 13.14 &amp;amp; 67.82 &amp;amp; 374.315 &amp;amp; N/A \\ \hline&lt;br /&gt;
        16 &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A &amp;amp; 0.9567 &amp;amp; 3.663 &amp;amp; 14.84 &amp;amp; 64.07 &amp;amp; 294.9 &amp;amp; N/A \\ \hline&lt;br /&gt;
        32 &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A &amp;amp; 3.785 &amp;amp; 14.70 &amp;amp; 57.52 &amp;amp; 239.5 &amp;amp; N/A \\ \hline&lt;br /&gt;
        64 &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A &amp;amp; 14.54 &amp;amp; 55.85 &amp;amp; 223.2 &amp;amp; 911.6 \\ \hline&lt;br /&gt;
    \end{tabular}&lt;br /&gt;
    \caption{Время работы в сек., $T = 2$ нити}&lt;br /&gt;
    \label{tab:2thr}&lt;br /&gt;
\end{table}&lt;br /&gt;
\begin{table}[h]&lt;br /&gt;
    \centering&lt;br /&gt;
    \begin{tabular}{|c|c|c|c|c|c|c|c|c|c|}&lt;br /&gt;
    \hline&lt;br /&gt;
        P \textbackslash N &amp;amp; 256 &amp;amp; 512 &amp;amp; 1024 &amp;amp; 2048 &amp;amp; 4096 &amp;amp; 8192 &amp;amp; 16384 &amp;amp; 32768 &amp;amp; 65536  \\ \hline&lt;br /&gt;
        2 &amp;amp; N/A &amp;amp; 0.1687 &amp;amp; 0.6239 &amp;amp; 2.654 &amp;amp; 13.17 &amp;amp; 76.14 &amp;amp; 487.3 &amp;amp; N/A &amp;amp; N/A \\ \hline&lt;br /&gt;
        4 &amp;amp; N/A &amp;amp; 0.1299 &amp;amp; 0.4685 &amp;amp; 1.905 &amp;amp; 8.141 &amp;amp; 40.02 &amp;amp; 214.4 &amp;amp; 1154.6 &amp;amp; N/A \\ \hline&lt;br /&gt;
        8 &amp;amp; N/A &amp;amp; N/A &amp;amp; 0.2759 &amp;amp; 0.9752 &amp;amp; 3.870 &amp;amp; 16.59 &amp;amp; 75.56 &amp;amp; 374.2 &amp;amp; N/A \\ \hline&lt;br /&gt;
        16 &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A &amp;amp; 1.058 &amp;amp; 3.783 &amp;amp; 15.13 &amp;amp; 62.32 &amp;amp; 269.9 &amp;amp; N/A \\ \hline&lt;br /&gt;
        32 &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A &amp;amp; 3.915 &amp;amp; 14.96 &amp;amp; 59.65 &amp;amp; 242.9 &amp;amp; N/A \\ \hline&lt;br /&gt;
        64 &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A &amp;amp; N/A &amp;amp; 15.28 &amp;amp; 59.28 &amp;amp; 236.4 &amp;amp; 955.5 \\ \hline&lt;br /&gt;
    \end{tabular}&lt;br /&gt;
    \caption{Время работы в сек., $T = 3$ нитей}&lt;br /&gt;
    \label{tab:3thr}&lt;br /&gt;
\end{table}&lt;br /&gt;
&lt;br /&gt;
Видим, что ускорение уже на 2 процессах существенно больше чем в 2 раза по сравнению с последовательной программой.&lt;br /&gt;
&lt;br /&gt;
Ниже на Рисунках \ref{graph:3d_1}, \ref{graph:3d_2}, \ref{graph:3d_4} приведены зависимости из таблиц выше.&lt;br /&gt;
&lt;br /&gt;
\begin{figure}[h] \centering&lt;br /&gt;
\includegraphics[scale=0.45]{pics/pic_T1.png}&lt;br /&gt;
\caption{$T = 1$}&lt;br /&gt;
\label{graph:3d_1}&lt;br /&gt;
\end{figure}&lt;br /&gt;
\begin{figure}[h] \centering&lt;br /&gt;
\includegraphics[scale=0.45]{pics/pic_T2.png}&lt;br /&gt;
\caption{$T = 1$}&lt;br /&gt;
\label{graph:3d_2}&lt;br /&gt;
\end{figure}&lt;br /&gt;
\begin{figure}[h] \centering&lt;br /&gt;
\includegraphics[scale=0.45]{pics/pic_T3.png}&lt;br /&gt;
\caption{$T = 1$}&lt;br /&gt;
\label{graph:3d_4}&lt;br /&gt;
\end{figure}&lt;br /&gt;
&lt;br /&gt;
На Рисунке \ref{graph:2d_P} приведены графики зависимости времени работы программы от количества процессов при фиксированном размере матрицы $N \in \{4096, 81921\}$ и количество потоков $T = 1$.&lt;br /&gt;
Видим, что на малом числе процессов ускорение почти линейное, затем достигается некоторая граница и время начинает несколько расти из-за увеличения накладных расходов.&lt;br /&gt;
Также это может быть связано с тем, что процессы располагаются на большем числе отдельных узлов и скорость передачи сообщений между ними меньше, чем внутри одного узла.&lt;br /&gt;
&lt;br /&gt;
\begin{figure}[h] \centering&lt;br /&gt;
\begin{subfigure}[b]{0.45\textwidth}&lt;br /&gt;
\includegraphics[scale=0.55]{pics/graph_N4.png}&lt;br /&gt;
\caption{$N = 4096$}&lt;br /&gt;
\end{subfigure}&lt;br /&gt;
\hfill&lt;br /&gt;
\begin{subfigure}[b]{0.45\textwidth}&lt;br /&gt;
\includegraphics[scale=0.55]{pics/graph_N5.png}&lt;br /&gt;
\caption{$N = 8192$}&lt;br /&gt;
\end{subfigure}&lt;br /&gt;
\caption{$time(P)$}&lt;br /&gt;
\label{graph:2d_P}&lt;br /&gt;
\end{figure}&lt;br /&gt;
&lt;br /&gt;
На Рисунке \ref{graph:2d_T} приведены графики зависимости времени работы программы от количества процессов при фиксированном размере матрицы $N = 81921$ и различном количестве потоков $T \in \{1, 2, 4\}$.&lt;br /&gt;
Можем заметить, что использование двух потоков дает практически двукратный выигрыш во времени по сравнению с одним, однако 4 потока работают уже дольше.&lt;br /&gt;
Вероятно, это связано с накладными расходами на их синхронизацию и менее равномерным распределением задач.&lt;br /&gt;
&lt;br /&gt;
\begin{figure}[h] \centering&lt;br /&gt;
\includegraphics[scale=0.75]{pics/graph_T123.png}&lt;br /&gt;
\caption{$N = 8192$}&lt;br /&gt;
\label{graph:2d_T}&lt;br /&gt;
\end{figure}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
\section{Выводы}&lt;br /&gt;
&lt;br /&gt;
В рамках практической работы мы реализовали параллельную версию алгоритма QR-разложения квадратной вещественной матрицы методом вращений Гивенса и изучили масштабируемость написанной программы.&lt;br /&gt;
Из результатов проделанной работы можем сделать следующие выводы:&lt;br /&gt;
\begin{enumerate}&lt;br /&gt;
    \item Реализованная нами программа выполняет поставленную задачу.&lt;br /&gt;
    \item Параллельная программа по сравнению с последовательной версией ускоряется в большее число раз, чем число процессов - это говорит о хороших параллельный свойствах алгоритма.&lt;br /&gt;
    \item На малом (от 2 до 8) количестве процессов имеет место почти линейное ускорение программы.&lt;br /&gt;
    \item При большом количестве процессов ($\geq 16$) или потоков (4) на исследованных размерах матриц программа заметно не ускоряется, так как накладные расходы и несбалансированность &amp;quot;перевешивают&amp;quot; ускорение.&lt;br /&gt;
\end{enumerate}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[En: Givens method]]&lt;/div&gt;</summary>
		<author><name>Bulatral</name></author>
	</entry>
</feed>