Mercurial > hg > Papers > 2008 > gongo-ess
view task_manager.tex @ 3:3ee6deaab278
*** empty log message ***
author | gongo |
---|---|
date | Mon, 14 Jul 2008 16:28:37 +0900 |
parents | d40dd97c0a50 |
children | 6458878b4526 |
line wrap: on
line source
\section{Task Manager} \label{sec:tm} Task Manager は、Task と呼ばれる分割された各プログラムを管理する。 Task の単位はサブルーチンまたは関数とし、Task 同士の依存関係を考慮しながら 実行していく。 現在実装されている Task Manager の API を \tabref{tab:tm-api} に示す。 \begin{table}[htbp] \caption{Task Manager API} \label{tab:tm-api} \hbox to\hsize{\hfil \begin{tabular}{c|l} \hline \hline create\_task & Task を生成する \\ \hline run & 実行 Task Queue の実行 \\ \hline allocate & 環境のアライメントを考慮した allocater \\ \hline \hline add\_inData & Task への入力データのアドレスを追加 \\ \hline add\_outData & Task からのデータ出力先アドレスを追加 \\ \hline add\_param & Task のパラメータ (32 bits) \\ \hline wait\_for & Task の依存関係の考慮 \\\hline set\_cpu & Task を実行する CPU の設定 \\ \hline spawn & Task を実行 Task Queue に登録する \\ \hline \end{tabular}\hfil} \end{table} 以下に Task Manager を使った記述例を示す。 {\small \begin{verbatim} char sendStr[STRSIZE] = "Hello, World"; int main(void) { TaskManager *manager; Task *task; int length = sizeof(char)*STRSIZE; /** * Constructor * @param CPU_NUM: 使用する CPU の数 */ manager = new TaskManager(CPUNUM); // TASK_RUN = TaskRun::run に対応する ID task = create_task(TASK_RUN); /** * タスクの入出力データの設定 * @param[1]: address of data * @param[2]: size of data */ task->add_inData(sendStr,length); task->add_outData(sendStr,length); task->spawn(); printf("before: %s\n", sendStr); manager->run(); printf("after : %s\n", sendStr); return 0; } TaskRun::run(void *rbuf, void *wbuf) { // add_inData で指定したアドレスのデータを取得 // recvStr = "Hello, World" char *recvStr = (char*)get_input(rbuf, 0); // fixStr にあるデータが、 // Task 終了後、add_outData で指定した // sendStr に書き込まれる char *fixStr = (char*)get_input(wbuf, 0); strcpy(fixStr, recvStr); fixStr[0] = 'D'; fixStr[3] = 'E'; } // 実行結果 before: Hello, World after: DelEo, World \end{verbatim} } \subsection{Task の定義} \label{sec:tm-task} タスクの定義を以下に示す。 {\small \begin{verbatim} class Task { public: int command; ListData inListData; // list of input data ListData outListData; // list of output area Task* self; int param_size; int param[3]; }; class HTask : public Task { public: // List of task waiting for me TaskQueuePtr wait_me; // List of task for which I am waiting TaskQueuePtr wait_i; CPU_TYPE cpu_type; }; \end{verbatim} } Task クラスは、各 Core が実行するタスクの単位オブジェクトである。 各 Core は inListData にあるアドレス(メインメモリ)からデータを取得し、 command に対応するコードを実行し、結果を outListData にあるアドレスに送信する。 これらの処理はパイプラインに沿って動作する (sec:\ref{sec:tm-scheduler})。 各 Core には Task の集合である TaskList を送る。 HTas クラスは、TaskManager で管理する実行前のタスクオブジェクトである。 wait\_me, wait\_i はタスク依存の条件 (sec:\ref{sec:tm-depend}) に、 cpu\_type は実行する CPU の切り替え (sec:\ref{sec:tm-cpu}) に用いる。 \subsection{スケジューラ} \label{sec:tm-scheduler} TaskManager のスケジューラを以下に示す。 \begin{verbatim} SchedTaskBase *task1, *task2, *task3; do { task3->write(); task2->exec(); task1->read(); taskTmp = task3; task3 = task2; task2 = task1; task1 = task1->next(taskTmp); } while (task1); \end{verbatim} TaskList にある Task が全て終了し、メインスレッドから 終了のメッセージを受け取ったら、while 文を抜ける。 SchedTaskBase クラスは、スケジューラによって実行されるオブジェクトである。 スケジューラ自身のタスクとして、以下のようなタスクがある。 これらのオブジェクトは全て SchedTaskBase を継承している。 \begin{itemize} \item SchedMail: メインスレッドからのメッセージを取得する \item SchedTaskList: TaskList を取得する \item SchedTask: Task を実行する \item SchedExit: Core の実行を終了する \item SchedNop: 何も行わない \end{itemize} ユーザがタスクを記述する場合、SchedTask を継承し、exec() 内の run() に タスクの処理を記述する。 \subsection{タスク依存} \label{sec:tm-depend} Task Manager はタスク依存を解決する機能を持っている。 以下は記述例である。 \begin{verbatim} // task2 は task1 が終了してから開始する task2->wait_for(task1); \end{verbatim} タスク依存が満たされたタスクを ActiveQueueに入れる。 各 Core は ActiveQueue から処理するコードとデータを取得し、 自律的に実行する。終了したタスクはメインスレッドへ終了のコマンドを 発行紙、メインスレッドはそれを見て WaitQueue のタスクを調べ、 タスク依存を満たしたタスクを ActiveQueue に入れる。 \subsection{タスクを実行させる Core の選択} \label{sec:tm-cpu} TaskManager の \verb|set_cpu()| により、タスクを どの Core で実行するか選択する事が出来る。 \begin{verbatim} // Core 1 で実行する task->set_cpu(CPU_1); // どの Core で実行してもいい task->set_cpu(CPU_ANY); \end{verbatim} メインスレッド内でもタスクを実行する事が可能なため、これを用いる事により、 環境依存によるプログラム変換はタスクの部分だけとなり、全体の変換は必要ない。 このことはデバッグにおいても有効であると言える。 デバッグが困難な並列プログラムの前に、まずはメインスレッド単体で動く シーケンシャルプログラムを記述する。 仕様やアルゴリズムの正しさを確認できたら、\verb|set_cpu| により 各 Core へタスクを渡し、並列プログラムへ移行する。 デバッグに関する詳細は第 \ref{sec:debug} 節で述べる。 \subsection{メインスレッドと各 Core 間との同期} メインスレッドと各 Core 間では、32 ビットメッセージの 交換により同期を行っている。これは Cell の機能の一つである、 Mailbox を元に実装した。 Mailbox とは SPE の MFC 内の FIFO キューであり、 PPE と SPE 間の 32 ビットメッセージの交換に用いられる \cite{mailbox} 。 通常、スレッド間で待ち合わせを行うと処理が止まってしまい、 並列度が下がってしまうことがあるが、Mailbox は メッセージ交換なので待ち合わせを避けることが可能である。 \figref{fig:sync} は、Cerium における PPE-SPE 間のメッセージのやりとりを表している。 メインスレッドでは、各 SPE の起動と終了、そしてタスクの管理を行っている。 そして Outbound Mailbox (SPE $\rightarrow$ PPE メッセージ) を見て、 その内容に対応する処理を PPE 上で行う。 対処した結果を Inbound Mailbox (PPE $\rightarrow$ SPE メッセージ) で伝え、 受け取った SPE はタスクを再実行する。 \begin{figure}[tb] \begin{center} \includegraphics[scale=0.353]{figure/sync.pdf} \caption{PPE, SPE threads} \label{fig:sync} \end{center} \end{figure}