Mercurial > hg > Papers > 2010 > program-symposium
view 14.tex @ 0:a9fda18657b3 default tip
add
author | Shinji KONO <kono@ie.u-ryukyu.ac.jp> |
---|---|
date | Wed, 16 Dec 2009 10:05:04 +0900 |
parents | |
children |
line wrap: on
line source
\section{ 比較} ここでは、SPURS Engine、Open CL、並列処理言語であるErlang/Scalaと比較してみる。 Cerium は、SPURS Engineの実装の一つであるが、SPURS Engine 自体の情報が 公開されていないので比較することは難しい。Cerium は、sourceforge.jp 上で公開されている。 Cerium では、SPE Task の終了を Mail により PPE に投げて、PPE側で Task Queue の管理を行っている。Word countのような場合は、それが負荷になる 場合もある。SPURS Engine 等で、どのような工夫が行われているのかは 未知数である。 Open CLは、Taskを登録する代わりに、文字列として渡し、Open CL側で、 GPGPUやThreadに展開される。Mac OS X では、llvm \cite{llvm} を用いて 展開されている。Cerium では、前もってコンパイルされた関数をマクロ により表に登録する仕組みである。 Kernel は、 {\small \begin{verbatim} __kernel void UniformAddKernel( __global float *output_data, __global float *input_data, __local float *shared_data, const uint group_offset, const uint base_index, const uint n) { const uint local_id = get_local_id(0); const uint group_id = get_global_id(0) / get_local_size(0); const uint group_size = get_local_size(0); \end{verbatim} } と言う形を持っている。globalがメインメモリ上のデータで、localが Local Store のデータである。 データのload/storeは、Kernel として明示的に記述し、 {\small {\small \begin{verbatim} unsigned int k = PRESCAN_NON_POWER_OF_TWO; clSetKernelArg(ComputeKernels[k], a++, sizeof(cl_mem), &input_data); clEnqueueNDRangeKernel(ComputeCommands, ComputeKernels[k], 1, NULL, global, local, 0, NULL, NULL); \end{verbatim} } } 等として、自分で Queue を管理する。実行 Queue に自分で格納する Open CL の方がやや繁雑な記述となる。 Kernel 間の依存関係は、Queue で解決されているので、起動する メインルーチン側で処理することになる。 Kernelの中では、\_\_globalと言う形で、いつでもメインメモリにアクセスする ことが可能である。なので、明示的なDMAは必要ない。しかし、見えないだけで コストや待ち時間は生じてしまう。そこで、Pre Scan のような形で、 データを \_\_local に前もって持って来ておく必要がある。 \subsection{ Erlang, Scala} Erlang と Scala は、Actor に似た感じで並列処理を行う。Erlang は、 Prolog に似た構文を持っており、Scala は Java 上に実装されている。 双方の言語とも通信はチャネルで行われて、Task 上のデータは関数型 言語的な意味で変更されない。再帰的な関数呼び出しにより、Taskの 状態を作るので、両方とも似たような(構文は隨分違うが)並列プログラミング のスタイルを提供している。 Erlang {\small \begin{verbatim} ping(N, Pong_PID) -> Pong_PID ! {ping, self()}, receive pong -> io:format("Ping received pong~n", []) end, ping(N - 1, Pong_PID). \end{verbatim} } Scala {\small \begin{verbatim} class Counter extends Actor { override def act(): Unit = loop(0) def loop(value: Int): Unit = { receive { case Incr() => loop(value + 1) case Value(a) => a ! value; loop(value) case Lock(a) => a ! value receive { case UnLock(v) => loop(v) } case _ => loop(value) } } } \end{verbatim} } Cerium では、データの引き渡しは、void *にcastするので、型の 安全性を言語上で保証することは出来ない。Open CL でも状況は 同じで、 {\small \begin{verbatim} __kernel void UniformAddKernel( __global float *output_data, __global float *input_data, __local float *shared_data, const uint group_offset, const uint base_index, const uint n) \end{verbatim} } が、 {\small {\small \begin{verbatim} err |= clSetKernelArg(ComputeKernels[k], a++, sizeof(cl_mem), &output_data); err |= clSetKernelArg(ComputeKernels[k], a++, sizeof(cl_mem), &partial_sums); err |= clSetKernelArg(ComputeKernels[k], a++, sizeof(float), 0); err |= clSetKernelArg(ComputeKernels[k], a++, sizeof(cl_int), &group_offset); err |= clSetKernelArg(ComputeKernels[k], a++, sizeof(cl_int), &base_index); err |= clSetKernelArg(ComputeKernels[k], a++, sizeof(cl_int), &n); err |= clEnqueueNDRangeKernel(ComputeCommands, ComputeKernels[k], 1, NULL, global, local, 0, NULL, NULL); \end{verbatim} } } などに相当することになる。これらの型を(IDE等で)チェックすることは、難しくはないが、 Scala のよう言語自体がチェックする方が望ましい。 Erlang, Scala は、GCを持っているが、Open CL/Cerium では、明示的なメモリ管理 を行う必要がある。 パイプライン処理をうまく動作させるには、Taskの列(Queue)を作る必要があるが、Erlang、 Scala では、その列を明示的に作ることは強制されていない。分散処理、あるいは、 Thread による並列処理には、Erlang, Scala が適しているが、Many Core では、さらになんらかの構文的なサポートが必要だと考えられる。