\documentclass[12pt,fleqn,a4paper]{book}
% 
% Packages used:
%
\usepackage[koi8-r]{inputenc}
\usepackage[russian,english]{babel}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{amsfonts}
\usepackage{amsthm}
\advance\textheight by 5mm
%
% Common customization:
%
\input defs
%
% The document
%
\begin{document}
\selectlanguage{russian}
\lecture{10}{Параллельные вычисления}{С. Федин}
%
% The lecture
%

Хочется, параллелизовав полиномиальные по времени вычисления,
получить от этого <<экспоненциальную выгоду>>. 
Пусть вычисление занимало $\poly(n)$ времени; 
тогда мы хотим, имея $\poly(n)$ процессоров,
проделать это вычисление за $\poly(\log n)$.

Заметим, что если мы научимся это делать для большего числа процессоров,
то для меньшего мы это сделать тоже сможем: будем объединять 
несколько процессоров в один и моделировать на нем работу каждого из них
по очереди (чередуя шаги, делаемые за разные процессоры).
Таким образом, нам надо параллелизовать вычисления <<как можно сильнее>>,
оставив общий объем работы разумным (т.е. полиномиальным).

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

Однако, схема способна обрабатывать лишь входы заданной длины.
Поэтому мы будем рассматривать \emph{семейства схем}, по одной для
каждой возможной длины входа. Чтобы такое семейство можно было
использовать автоматически, необходимо, чтобы можно было
\emph{эффективно построить схему} для заданной длины входа.
Мы будем требовать, чтобы существовала детерминированная машина Тьюринга,
порождающая схему (в разумном виде --- так, чтобы ее вычисления можно
было моделировать на машине Тьюринга, которой она будет задана вместе
со входом) для входов длины $n$ по записи числа $n$ в
<<палочной>> системе счисления и при этом использующая лишь $O(\log n)$
ячеек памяти (и, следовательно, лишь полиномиальное от $n$ время). 
Такие семейства схем называются \emph{равномерными}.
Очевидно, что равномерные схемы --- полиномиального размера.

\begin{definition}
\begin{eqnarray*}
\NC^i &=& \left\{L \,\left|\, \textrm{\parbox{10cm}{$L$ принимается некоторым равномерным семейством схем глубины $O(\log^in)$, где $n$ --- размер входа}} \right.\right\}.\\[+5pt]
\NC &=& \bigcup_i\NC^i.
\end{eqnarray*}
\end{definition}

Хочется понять взаимосвязь нового класса с уже известными классами.
Очевидно, что $\NC \subseteq \P$ (является ли это включение строгим --- 
открытый ворпос). 

\begin{theorem}
$\NC^1 \subseteq \DSPACE(\log) \subseteq \NSPACE(\log) \subseteq \NC^2$.

\begin{proof}
1. Второе включение очевидно.


2. Докажем последнее включение, т.е. что $\NSPACE(\log) \subseteq \NC^2$.
Рассмотрим язык, лежащий в $\NSPACE(\log)$, и недетерминированную машину 
Тьюринга $M$, которая его принимает. 
\emph{Псевдоконфигурацией} в этой теореме назовем конфигурацию,
в которую не включается входная лента.
Рассмотрим граф всех возможных псевдоконфигураций машины $M$
(наличие каждого из ребер в этом графе, очевидно, зависит от входа).
Наша задача --- проверить, есть ли (для данного входа) в этом графе
путь из начальной псевдоконфигурации в принимающую.
Заметим, что размер псевдоконфигурации ограничен $O(\log)$.
Таким образом, нам надо решить задачу достижимости в графе, содержащем
полиномиальное число вершин (обозначим это число через $k$).

Покажем, что эта задача лежит в $\NC^2$. 
Пусть $A$ --- матрица смежности нашего ориентированного графа.
Наша задача --- вычислить булеву степень\footnote{Булево произведение
матриц --- это произведение, в котором в качестве операции сложения
используется дизъюнкция, а качестве операции умножения --- конъюнкция.} 
$A^k$ схемами глубины $\log^2k$.

Заметим, что для этого достаточно $\log k$ последовательных умножений 
пар матриц: $A^2$, $(A^2)^2$, \ldots (если получим в итоге чуть б\'ольшую
степень, чем $k$ --- а именно, ближайшую к $k$ сверху степень двойки ---
не страшно: $A^k=A^{k+1}=\ldots$). Для умножения же одной пары матриц 
$B\cdot C$ достаточно глубины схем $O(\log k)$:
параллельно вычислим все произведения вида $b_{il}\land c_{lj}$,
затем вычислим их суммы $\bigvee_{l=1}^k b_{il}\land c_{lj}$ также параллельно,
затрачивая на каждую сумму лишь логарифмическое время (глубину) ---
сначала складываем слагаемые по два, затем полученные частичные сумму ---
снова по два, и$\;$т$\;$д.

Итак, мы доказали, что достижимость в графе можно выяснить в $\NC^2$.
На вход этой схеме мы должны подать элементы матрицы смежности,
т.е. ребра графа ($1=\textrm{ребро есть}$, $0=\textrm{ребра нет}$).
Понятно, что для любых двух псевдоконфигураций мы можем в $\NC^2$ выяснить,
получена ли одна из них из другой (входом для этой схемы являются
биты входной ленты! псевдоконфигурации входами не являются: для каждой
интересной нам пары псевдоконфигураций мы строим свою подсхему).

3. Докажем теперь, что $\NC^1 \subseteq \DSPACE(\log)$. 
{\small\paragraph{Отступление.}
\leftskip 1cm
Пусть у нас имеется две машины Тьюринга $M_1$ и $M_2$,
вычисляющие некоторые функции и
затрачивающие лишь логарифмическую память на свои вычисления.
Нам хотелось бы вычислить композицию этих функций.

Первая мысль, которая приходит в голову --- сделать выходную ленту
одной машины входной лентой для другой и запустить их по очереди.
Однако так сделать нельзя, поскольку входная и выходная ленты
не учитываются в ограничении по памяти $M_1$ и $M_2$ и могут (и будут) иметь
длину большую, чем логарифм от входа первой машины;
при таком же объединении машин эти ленты становятся рабочими
и память на них следует учитывать.

Поэтому поступим иначе: вместо того, чтобы хранить выход $M_2$ и вход $M_1$,
мы будем хранить лишь счетчики позиций на соответствующих лентах, и
изменять их соответственно тому, как двигались бы головки.
Именно, запустим $M_2$ и, когда ей понадобится очередной $i$-й бит входа
($i$ хранится в ее счетчике), прервем ее вычисление, запустим $M_1$
(с самого начала) и дадим ей доработать до тех пор, пока ее счетчик
позиции на выходной ленте не станет равен $i$ и она захочет записать
на выходную ленту очередной символ\footnote{Можно считать, что в каждую
позицию выходной ленты символ записывается лишь однажды.}. Этот символ
мы и используем, чтобы совершить очередной переход (определить 
следующую конфигурацию) машины $M_2$, и продолжим вычисления.

}
\medskip

Пусть есть теперь язык $L \in \NC^1$. Докажем, что $L\in \DSPACE(\log)$. 
Пусть на вход нам подали $x$. Надо выяснить, верно ли, что
$x\in L$. Мы сделаем это, построив композицию трех функций, вычисляемых
с логарифмической памятью.

Первая функция строит схему для входов длины той же, что у $x$.
Существование соответствующей логарифмической по памяти машины Тьюринга
следует из определения $\NC^1$.

Вторая функция преобразует эту схему в формулу
(т.е. делает из ориентированного ациклического графа, 
представляющего схему, дерево). Сделаем
мы это следующим образом: каждый гейт формулы будет 
обозначаться битовой строчкой, обозначающей путь к некоторому гейту
исходной схемы от ее выходного гейта.
(В этой строчке 1 будет означать, что мы пошли направо, а 0 --- налево.)
Понятно, что перечислить эти вершины, их типы ($\lor, \land, \lnot$) 
и ребра между ними мы можем, ограничившись логарифмической памятью,
устроив в исходной схеме поиск в глубину (по ребрам, обратным заданным)
(правда, чтобы вернуться к предыдущей вершине, придется пройтись еще
раз от корня до нее, поскольку мы можем хранить только <<куда мы пошли>> ---
налево или направо --- но не <<откуда пришли>>).

Третья функция вычисляет значение формулы на входе $x$. 
Для этого будем обходить дерево и вычислять значение левого поддерева,
а, когда надо, и правого. <<Когда надо>> здесь означает
следующее: если вершина помечена <<$\lor$>>, 
то правое поддерево надо вычислять только в том случае, если значение в левом 
поддереве $0$, а если помечена <<$\land$>>, то надо вычислять 
только в том случае, если значение в левом поддереве $1$. 

То, что для этого достаточно логарифмической памяти, очевидно.
Вот, впрочем, формальное доказательство.

{\small
Заметим, что нам достаточно хранить только битовую строчку,
обозначающую текущий гейт $c$ и переменную $b$, принимающую одно из пяти значений:
\begin{enumerate}
\item[$b=1$,] \leftskip 3mm
              если мы приступили к обработке данной вершины, 
\item[$b=2$,] если уже обработали левое поддерево с результатом $0$,
\item[$b=3$,] если уже обработали левое поддерево с результатом $1$,
\item[$b=4$,] если уже обработали правое поддерево с результом $0$,
\item[$b=5$,] если уже обработали правое поддерево с результом $1$.
\end{enumerate}
Начинаем мы с пустой строки $c$ (соответствующей выходному гейту)
и $b=1$. Опишем работу алгоритма на гейте, вычисляющем <<$\land$>> 
(для <<$\lor$>> --- аналогично). 
\begin{description}
\item[Если] $b=1$, то $c:=c\,0$, $b:=1$;
\item[если] $b=2$, то <<обрезаем>> последний бит $c$, и 
\begin{description}
\item[если] этот бит был $0$, то $b:=2$, а
\item[если] этот бит был $1$, то $b:=4$;
\end{description}
\item[если] $b=3$, то $c:=c1, b:=1$;
\item[если] $b=4$, то поступаем аналогично случаю $b=2$;
\item[если] $b=5$, то <<обрезаем>> последний бит $c$ и
\begin{description}
\item[если] этот бит был $0$, то $b:=3$, а
\item[если] этот бит был $1$, то $b:=5$.
\end{description}
\end{description}
Понятно, что памяти мы затратили снова лишь $O(\log)$ на хранение $c$~и~$b$.
}

\end{proof}


\end{theorem}


\end{document}
