Опубликован: 23.04.2013 | Доступ: свободный | Студентов: 856 / 185 | Длительность: 12:54:00
Лекция 4:

Параллельные алгоритмы

Вычисление тригонометрических функций

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

С программистской точки зрения задача сводится к вычислению бесконечной суммы сходящегося ряда:

f(x)=\sum^{\mathcal1}_{i=0}a_i ( 3.5)

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

a_{k+1}=g(a_k) ( 3.6)

Учитывая сходимость a_k, бесконечная сумма заменяется конечной суммой, когда суммирование заканчивается при условии, что a_k по модулю становится меньше заданной точности вычислений \varepsilon. В другом варианте задаются достаточно большим значением N и вычисления прекращаются при i равном N.

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

\sum a_i=\sum  a_{i1}+\sum  a_{i2}+…+\sum a_{ip} ( 3.7)

Если при суммировании задавать N и выбирать его кратным P (N = k * P), то каждый из процессоров может вычислять k членов суммы. Например, первый процессор будет вычислять сумму первых k членов, второй - суммирует следующую группу из k членов и так далее. Этот алгоритм распараллеливания мы называем сегментным алгоритмом. Другой способ распараллеливания вычислений, называемый шаговым алгоритмом, состоит в том, что процессоры суммируют члены ряда, отстоящие друг от друга на расстоянии P.

Последовательный алгоритм зачастую имеет несомненные достоинства, состоящие в том, что во многих практически значимых задачах удается построить простую рекуррентную формулу для вычисления a_k и простую формулу для вычисления начального члена суммы. Поскольку вычисление a_k может требовать сложных вычислений, то применение простых рекуррентных соотношений позволяет существенно увеличить эффективность последовательного алгоритма. При применении сегментного алгоритма распараллеливания удается сохранить рекуррентную формулу, применяемую в последовательном алгоритме. Однако для каждого процессора необходимо вычислить начальное значение и это существенно снижает эффект, получаемый за счет распараллеливания. Для шагового алгоритма начальные значения вычисляются не столь сложно, но рекуррентная формула становится намного сложнее. Это типичная картина - распараллеливание требует жертв - усложнения алгоритма. В результате может оказаться, что привлечение дополнительных процессоров может приводить не к снижению времени вычислений, а к его росту. В подобных задач может существовать оптимальное число процессоров p*, после достижения которого время вычислений начнет возрастать.

Приведем пример, демонстрирующий указанные проблемы. В качестве функции f(x) выберем функцию ArcSin(x), для которой справедливо следующее разложение в ряд Тэйлора:

ArcSin(x)=x+\frac{x^3}{2 \cdot 3}+\frac{1 \cdot 3 \cdot x^5}{2 \cdot 4 \cdot  5}+…+\frac{1 \cdot 3 \cdot 5 \cdot …(2\cdot n-1)x^{2n+1}}{2 \cdot 4 \cdot 6 \cdot …(2 \cdot n)\cdot (2\cdot  n+1)}+… ( 3.8)

Точная формулировка задачи состоит в следующем. Дано вещественное число x такое, что |x| \le 1. Требуется найти с заданной точностью значение функции ArcSin(x), вычисляемое как сумма бесконечного сходящегося ряда (3.8). Нетрудно получить следующие соотношения:

a_0=x
a_i=\frac{(2i-1)!!}{(2i)!!\cdot(2i+1)}x^{2i+1} ( 3.9)
a_{i+1}=a_i\cdot \frac{k}{(k+1)\cdot(k+2)}x^2,

где k=2i+1

Последовательный алгоритм построен на шаблоне, заданном алгоритмом 3.5. Отличие состоит в том, что добавляется аргумент x и текущий член вычисляется в соответствии с рекуррентной формулой, заданной соотношением (3.9):

double x2 = x * x;
            double i = 0;
            double a = x;   //начальное значение 
            double k = 0;
            S = 0;
            while (Math.Abs(a) > eps)
            {
                S += a;                
                k = 2 * i + 1;
                a *= k * k * x2 / ((k + 1) * (k + 2));
                i++;
            }
Листинг 3.7. Последовательный алгоритм вычисления функции Arcsin(x)

Шаговый алгоритм можно строить на основе шаблона, заданного алгоритмом 3.6. Нужно лишь корректно задать начальные значения и применить для вычисления общего члена рекуррентную формулу, связывающую i-й и i+p -й члены ряда. Для функции Arcsin(x) эта формула имеет вид:

a_{i+p}=a_i \cdot \frac{2i+1}{2i+2p+1}\cdot x^{2p}\cdot \prod^{p}_{k=1}(1-\frac{1}{2 \cdot (i+k)} ( 3.10)

Вот код соответствующего алгоритма:

double x2 = x * x;
            double i = 0;            
            double k = 0;
            double[] y = new double[count_p];
            double a = 0;
            double x2p = x2;

            //Вычисление начальных значений 
            y[0] = x;
            for (int j = 1; j < count_p; j++)
            {
               k = 2 * i + 1;
               y[j] = y[j - 1] * k * k * x2 / ((k + 1) * (k + 2));                
               i++;
               x2p *= x2;
            }

            for(int j = 0; j < count_p; j++)
            {
                a = y[j];
                i = 0;
                double pr = 1;
                while (Math.Abs(a) > eps)
                {
                    pr = 1;                   
                    for(int r = 1; r <= count_p; r++)
                        pr *= (1- 1/(2*(i + r)));
                    a *= pr * (2*i + 1)/((2*(i +count_p) +1)) * x2p;                    
                    y[j] += a;
                    i += count_p;
                }
            }
            S = y[0];
            for (int j = 1; j < count_p; j++)
                S += y[j];
Листинг 3.8. Шаговый алгоритм вычисления функции Arcsin(x)

Конечно формула (3.10) существенно сложнее, чем формула (3.9) для последовательного алгоритма. Это плата за распараллеливание, которая может оказаться чрезмерной в ряде случаев. Результаты численных экспериментов для данной конкретной функции будут приведены в последующих главах.

Для сегментного алгоритма рекуррентное соотношение дается формулой (3.9), как и для последовательного алгоритма. Но вычисление начального значения для соответствующего сегмента потребует серьезных вычислительных затрат в сравнении с последовательным алгоритмом. Поэтому для задач, подобных вычислению функции ArcSin(x), использование p процессоров не даст выигрыша во времени в p раз. Не буду приводить код сегментного алгоритма, полагая, что он достаточно понятен.

Алексей Рыжков
Алексей Рыжков

не хватает одного параметра:

static void Main(string[] args)
        {
            x = new int[n];
            Print(Sample1,"original");
            Print(Sample1P, "paralel");
            Console.Read();
        }

Никита Белов
Никита Белов

Выставил оценки курса и заданий, начал писать замечания. После нажатия кнопки "Enter" окно отзыва пропало, открыть его снова не могу. Кнопка "Удалить комментарий" в разделе "Мнения" не работает. Как мне отредактировать недописанный отзыв?