Распараллеливание схемы Горнера

Постановка задачи и суть проблемы

Классический пример рекурсии в алгоритме - схема Горнера для вычисления частного от деления многочлена n-й степени одной переменной Pn(x) = a0xn + a1xn-1 + ... + an-1x + a на двучлен x - c, с получением многочлена n-1-й степени Qn-1(x) = b0xn-1 + b1xn-2 + ... + bn-2x + bn-1 и остатка от деления bn (равного Pn(с)). При этом у нас искомые коэффициенты и остаток вычисляются по рекуррентной формуле: bi = c bi-1 + ai.

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

Схема сдваивания

Пусть нам нужно вычислить все частичные суммы элементов yi от 1 до k, где 1 ≤ k ≤ n. Оказывается, что это возможно сделать по схеме сдваивания примерно за log2n шагов, выполняя на каждом шаге примерно по n/2 сложений. Приведём пример вычисления всех частных сумм для n=8 за 3 шага.

1-й шаг: вычисляем y1 + y2, y3 + y4, y5 + y6, y7 + y8.
2-й шаг: вычисляем (y1 + y2) + y3 , (y1 + y2) + (y3 + y4), (y5 + y6) + y7, (y5 + y6) + (y7 + y8).
3-й шаг: вычисляем (y1 + y2 + y3 + y4) + y5, (y1 + y2 + y3 + y4) + (y5 + y6), (y1 + y2 + y3 + y4) + (y5 + y6 + y7), (y1 + y2 + y3 + y4) + (y5 + y6 + y7 + y8)

Как видим, все частные суммы вычислены. Отмечаем про себя то, что

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

Распараллеливание схемы Горнера

Перепишем схему Горнера в векторной формулировке. Введём последовательность векторов zi = ( bi, 1)T. Тогда рекуррентные формулы схемы Горнера примут вид: zi = Aizi-1, где Ai - треугольная матрица вида ( (c, 0)T, (ai, 1)T). После подстановки всех формул мы видим, что zi = AiAi-1...A1z0, то есть нам нужно вычислить все частные матричные произведения AiAi-1...A1, после чего умножить их на вектор z0. Но матричное умножение ассоциативно, и к вычислениям частных произведений мы можем применить схему сдваивания, причём с упрощениями - ведь

  • нижние строки матриц нам вычислять не нужно - они все равны (0, 1);
  • в верхней строке матрицы первый элемент вычисляется за 1 умножение (второе слагаемое равно 0);
  • второй элемент первой строки вычисляется за 1 умножение и 1 сложение (второе умножение - на 1 - выполнять не нужно).
Вычисления векторов zi на последнем этапе тоже можно выполнять не полностью (потому что 1 не нужно вычислять), и, кроме этого, независимо друг от друга. Они тоже займут всего по 1 умножению и 1 сложению. При достаточно большом n схемой Горнера можно загрузить всё имеющееся у нас оборудование ПЛИС в эффективном режиме, без простоев.