% Lecture notes - Lecture 9 - 10/06/2003
% C. Scott Ananian
\providecommand{\color}[2][rgb]{}

\section{The {\em Cilk} Scheduler}
\seclabel{scheduler} In this section we review the {\em work-stealing} algorithm
of the {\em Cilk} scheduler. Recall that in {\em Cilk} a processor works on a
procedure $\alpha$ until one of three events occur:
\begin{enumerate}
\item Procedure \emph{$\alpha$ Spawns procedure $\beta$.}  In this case the
  processor pushes $\alpha$ on (the bottom of) the ready deque, and starts work
  on procedure $\beta$.
\item Procedure \emph{$\alpha$ Returns.}  There are three cases:
  \begin{enumerate}
  \item If the deque is nonempty, the processor pops a procedure (from the
    bottom) and begins working on it.
  \item If the deque is empty, the processor tries to execute $\alpha$'s parent
    (which is not in a deque).
  \item If the deque is empty and the processor is unable to execute $\alpha$'s
    parent (because the parent is busy), then the processor work steals.
  \end{enumerate}
\item Procedure \emph{$\alpha$ Syncs.} If there are no outstanding children,
  then continue: we're properly synced.  Otherwise (when there are no
  outstanding children), work steal.  Note that deque is empty in this case.
\end{enumerate}
When a processor begins work stealing it operates as follows:
\begin{enumerate}
\item Processor chooses a victim \emph{uniformly at random}.
\item If the victim's deque is empty, the processor repeats the first step.
\item Otherwise, the processor steals the top (oldest) thread from the victim's
  deque and begins to work on it.
\end{enumerate}
\noindent{\bf Note}: A thread can be in exactly one of three places:
\begin{enumerate}
\item in the deque,
\item on a processor being executed, or
\item waiting for predecessors to complete.
\end{enumerate}
In the following we use the notion of \defn{procedure} as composed of multiple
threads.

\section{Location of shallowest thread}
In this section we define the depth of a thread and the shallowest thread. Next
we prove that the shallowest thread on a processor is either at the top of a
deque or being executed.

\begin{definition}[Depth of thread] 
  The \defn{depth} $d(u_i)$ of a thread $u_i$ is the longest distance in the DAG
  from the root thread to $u_i$.
\end{definition} 

\begin{definition}[Shallowest Thread]
  The \defn{shallowest thread} is the thread with minimal depth.
\end{definition}

We prove some structural properties of the deque.
\begin{lemma} [Structural lemma]
  Consider any processor $p$ at time $t$.  Let $u_0$ be the current root thread
  (of procedure $\alpha_0$) executing on processor $p$.  Let $u_1, u_2, \ldots,
  u_k$ be the threads (of procedures $\alpha_1, \alpha_2,\ldots,\alpha_k$) in
  $p$'s deque ordered from bottom to top.  Let $d(u_i)$ be the depth of thread
  $u_i$ in DAG $G$.
\begin{figure}
  \begin{center}
    \input{9/lecture-9.1-fig1.pstex_t}
  \end{center}
  \caption{Deque of a processor.}
\end{figure}
Then,
\begin{equation*}
  d(u_0) \geq d(u_1) > \cdots > d(u_{k-1}) > d(u_k).
\end{equation*}
\end{lemma}

\begin{observation}
  A procedure is made up of multiple threads, but \emph{at a given time $t$},
  there is only one thread in the procedure which is being executed.
\end{observation}

\begin{corollary}
  The shallowest thread in a processor is either at the top of a deque or being
  executed.
\end{corollary}

\begin{proof}
  Proof is by {\em induction on actions}.  The {\bf base case} (deque is empty;
  $u_0$ executing) trivially satisfies the inequality.  Thereafter, only four
  possible actions can alter the currently-executing thread and/or the deque.
  We prove that each of these actions maintains the desired depth-ordering
  property.
\begin{itemize}
\item {\bf Case 1: Steal.}
  \begin{figure}
    \begin{center}
      \input{9/lecture-9.1-fig2.pstex_t}
    \end{center}
    \caption{Change in the deque of a processor when a procedure is stolen.}
    \figlabel{steal}
  \end{figure}
%TODO: topic sentence should say main idea
  A steal removes the top entry from the deque (see \figref{steal}).  The
  processor performing the steal begins executing $u_k$ with an empty deque,
  trivially satisfying the inequality as in the base case.  The processor from
  whom the thread was stolen also maintains the invariant, because if before the
  steal
\begin{equation*}
  d(u_0) \geq d(u_1) > \cdots > d(u_{k-1}) > d(u_k),
\end{equation*}
after the steal
\begin{equation*}
  d(u_0) \geq d(u_1) > \cdots > d(u_{k-1}).
\end{equation*}
Removing threads from anywhere in the deque cannot invalidate the ordering.

\item {\bf Case 2: Return.}
\begin{figure}
  \begin{center}
    \input{9/lecture-9.1-fig3.pstex_t}
  \end{center}
  \caption{Change in the deque of a processor when the processor returns
    from a procedure.}  \figlabel{return}
\end{figure}
A return removes the bottom entry from the deque (see \figref{return}). As in
the case of the steal, removing terms from the inequality cannot invalidate it.
If before the return
\begin{equation*}
  d(u_0) \geq d(u_1) > \cdots > d(u_{k-1}) > d(u_k),
\end{equation*}
afterwards we have
\begin{equation*}
  d(u_1) > \cdots > d(u_{k-1}) > d(u_k).
\end{equation*}
As before, removing threads from anywhere in the deque cannot invalidate the
ordering.

\item {\bf Case 3: Continue or sync.}
  %FIXME: not clear here with the figures (maybe put them one next to each other)
  \begin{figure}
    \begin{center}
      \input{9/lecture-9.1-fig4.pstex_t}
    \end{center}
    \caption{Change in the deque of a processor when the processor reaches a
      sync point or continues a procedure.}  \figlabel{sync}
  \end{figure}
  \begin{figure}
    \begin{center}
      \input{9/lecture-9.1-fig6.pstex_t}
    \end{center}
    \caption{A piece of a computation DAG.  Thread $u_0$ is followed by
      thread $u_a$ in procedure $\alpha_0$.}  \figlabel{dagpiece1}
  \end{figure} A sync or continue replaces the bottom entry of the deque (see
  \figref{sync}) with a thread with depth increased by one. A sync with one or
  more children outstanding becomes a steal, and is handled by Case 1.  If all
  children are complete, then a sync and a continue are identical: we replace
  the currently-executing thread $u_0$ with its successor in the DAG, $u_a$.
  \figref{dagpiece1} shows the relevant piece of the computation DAG.
  
  If the longest path to $u_0$ has length $d(u_0)$, then clearly there is a path
  to $u_a$ through $u_0$ which has length $d(u_0)+1$.  Therefore,
\begin{equation*}
  d(u_a) \geq d(u_0) + 1,
\end{equation*}
and since $d(u_0) \geq d(u_1)$, then
\begin{equation*}
  d(u_a) > d(u_1) > \cdots > d(u_{k-1}) > d(u_k).
\end{equation*}

\item {\bf Case 4: Spawn.}
  \begin{figure}
    \begin{center}
      \input{9/lecture-9.1-fig5.pstex_t}
    \end{center}
    \caption{Change in the deque of a processor when the processor spwans a
      procedure.}  \figlabel{spawn}
  \end{figure}
  \begin{figure}
    \begin{center}\input{9/lecture-9.1-fig7.pstex_t}\end{center}
    \caption{A piece of a computation DAG.  Thread $u_0$ is followed by
      thread $u_a$ in procedure $\alpha_0$, and spawns thread $u_s$ in procedure
      $\alpha_\text{spawn}$.}  \figlabel{dagpiece2}
  \end{figure} 
  A spawn replaces the bottom entry in the deque and also pushes a new procedure
  (see \figref{spawn}). \figref{dagpiece2} shows the portion of the computation
  DAG involved in a spawn.  Note that, since the only edges to $u_a$ and $u_s$
  come from $u_0$, that $u_a$ and $u_s$ have the same depth in the DAG, namely
  $d(u_0)+1$.  Therefore since $d(u_0)\geq d(u_1)$, then $d(u_s) = d(u_a) >
  d(u_1)$ and, as required
\begin{equation*}
  d(u_s) \geq d(u_a) > d(u_1) > \cdots > d(u_{k-1}) > d(u_k).
\end{equation*}
\noindent{\bf Note}: This is the only case where the depth of the currently-executing
thread may become equal to the depth of the thread on the bottom of the deque;
hence the only case where a shallowest thread may be the currently-executing
thread instead of the thread at the top of the deque.
\end{itemize}
Since the base case satisfies the property, and every action maintains the
property, then at any time $t$ in the execution for all deques
\begin{equation*}
  d(u_0) \geq d(u_1) > \cdots > d(u_{k-1}) > d(u_k).
\end{equation*}
\end{proof}

%TODO: restructure this section
\section{Critical threads}
In this section we construct an augmented computation DAG $G'$ similar to the
computation DAG $G$, such that when a thread has no incomplete predecessors in
$G'$, then it is at the top of a deque. Next, we introduce {\bf critical
  threads}.

Our goal is to show that any thread on the top of a deque cannot stay there long
because it will be stolen. 

\begin{definition}[Construction of $G\prime$]
  We create an augmented DAG $G\prime$ by making a (back) edge to every spawn
  thread from the parent's continuation thread.  Note that if a sync is
  immediately followed the spawn, we need to insert an extra continuation thread
  before the sync to prevent cycles in the DAG.  \figref{moddag} shows the
  execution graph of \figref{baddag} augmented with these edges.
  \begin{figure}
    \begin{center}\input{9/lecture-9.1-fig10.pstex_t}\end{center}
    \caption{An augmented DAG corresponding to the execution DAG of
      \figref{baddag}.  The dashed edges have been added from continuation
      threads to spawn threads.}\figlabel{moddag}
  \end{figure}
\end{definition}

We show that {\bf work-stealing} makes progress on the {\bf critical
  path}. Roughly speaking, $\Theta(P)$ steals decrease the critical path by a
constant. We require a stronger property for the computation DAG. 

\begin{proposition} [Necessary property]
  \label{prop:augdag}
  Whenever a thread has no incomplete predecessors in the augmented DAG
  $G\prime$, it is being executed or is on the top of some deque.
\end{proposition}

\noindent{\bf Note}: Proposition~\ref{prop:augdag} does not hold for the execution DAG
$G$, that is why we create $G\prime$.

\begin{example}
  To see why, consider the execution DAG in \figref{baddag}.  After
  threads $v_2$ and $v_1$ have been executed, we begin to execute thread $u_0$.
  We have the following deque:
  \begin{center}\input{9/lecture-9.1-fig9.pstex_t}\end{center}
  The thread $u_1$ has no unexecuted precedessors left in the DAG, but it is
  neither being executed nor at the top of the deque.
  \begin{figure}
    \begin{center}\input{9/lecture-9.1-fig8.pstex_t}\end{center}
    \caption{An execution DAG with threads $v_2$ and $u_2$ in procedure
      $\alpha_2$, threads $v_1$ and $u_1$ in procedure $\alpha_1$, and thread
      $u_0$ in procedure $\alpha_0$.}\figlabel{baddag}
  \end{figure}
\end{example}

\begin{observation}[Depth of Augmented DAG $G\prime$]
  The critical path of the augmented DAS $G\prime$ is $2T_\infty$. We can now
  get to a spawned thread via our back edge from the continuation edge, adding a
  distance of one to the longest path to the spawned thread. But we can only add
  one such extra edge to the path per spawn edge on the critical path of the
  original graph $G$. If we define the \defn{depth of a graph} $D(G)$ to be the
  depth of the deepest node, then,
  \begin{equation*}
    D(G) \leq D(G') \leq 2 D(G),
  \end{equation*}
  where $D(G)=T_\infty$.
\end{observation}

Proposition~\ref{prop:augdag} guarantees that whenever you execute a thread, you
will be executing one of the {\bf shallowest} threads in the augmented DAG
$G\prime$.  Hence, when you steal work (i.e. not otherwise making progress) you
make progress on the critical path by executing the threads which are shallowest.

\begin{definition}[Critical thread]
  \label{def:critthread}
  A thread is {\bf critical} if it has no un-executed predecessors in the augmented
  DAG $G\prime$.
\end{definition}

\noindent{\bf Note}: The extra edges in the augmented DAG $G\prime$ are not execution
edges, they simply induce extra ordering that we require for our performance
analysis of the {\em Cilk} scheduler.

%%% Local Variables: 
%%% mode: latex
%%% TeX-master: "lecture-9"
%%% End: 
