view slide/blank.html @ 64:4c245ed4e61a

io thread slide
author Yuhi TOMARI <yuhi@cr.ie.u-ryukyu.ac.jp>
date Wed, 18 Feb 2015 02:10:50 +0900
parents 3a35d13818e5
children a41beec3553e
line wrap: on
line source

<!DOCTYPE html>
<html>
  <head>
    <meta charset='utf-8'>
    <title>Seminar</title>

    <!-- 
         Notes on CSS media types used:
         
         1) projection -> slideshow mode (display one slide at-a-time; hide all others)
         2) screen     -> outline mode (display all slides-at-once on screen) 
         3) print      -> print (and print preview)
         
         Note: toggle between projection/screen (that is, slideshow/outline) mode using t-key

         Questions, comments?
         - send them along to the mailinglist/forum online @ http://groups.google.com/group/webslideshow    
      -->

    <!-- style sheet links -->
    <link rel="stylesheet/less" href="themes/blank/projection.css.less"  media="screen,projection">
    <link rel="stylesheet/less" href="themes/blank/screen.css.less"      media="screen">
    <link rel="stylesheet/less" href="themes/blank/print.css.less"       media="print">

    <link rel="stylesheet/less" href="blank.css.less"    media="screen,projection">

    <!-- Notes about less css support
         - all less stylesheets (*.css.less) need to get listed/loaded first (before the less.js script)
         - find more info about less.js online @ http://lesscss.org

         ***** NOTE:
         less.js browser script currently won’t work if you’re using Google Chrome
         and the path to your page starts with "file:///" due to a known Chrome issue.
         (In the developer/js console you will see:
         XMLHttpRequest cannot load file:///../s6/shared/projection.css.less.
         Cross origin requests are only supported for HTTP.)
      -->

    <!-- add js libs (less, jquery) -->
    <script src="js/less-1.1.4.min.js"></script>
    <script src="js/jquery-1.7.min.js"></script>

    <!-- S6 JS -->
    <script src="js/jquery.slideshow.js"></script>
    <script src="js/jquery.slideshow.counter.js"></script>
    <script src="js/jquery.slideshow.controls.js"></script>
    <script src="js/jquery.slideshow.footer.js"></script>
    <script src="js/jquery.slideshow.autoplay.js"></script>
    <script>
      $(document).ready( function() {
      Slideshow.init();
      
      // Example 2: Start Off in Outline Mode
      // Slideshow.init( { mode: 'outline' } );
      
      // Example 3: Use Custom Transition
      // Slideshow.transition = transitionScrollUp;
      // Slideshow.init();

      // Example 4: Start Off in Autoplay Mode with Custom Transition
      // Slideshow.transition = transitionScrollUp;
      // Slideshow.init( { mode: 'autoplay' } );
      } );
    </script>

    <!-- Better Browser Banner for Microsoft Internet Explorer (IE) -->
    <!--[if IE]>
        <script src="js/jquery.microsoft.js"></script>
        <![endif]-->

  </head>
  <body>

    <div class="layout">
      <div id="header"></div>
      <div id="footer">
        <div align="right">
          <img src="images/concurrency.png" width="200">
        </div>
      </div>
    </div>

    <div class="presentation">

      <!-- add slides here; example -->
      
      <div class='slide cover'>
        <table width="90%" height="90%" border="0" align="center">
          <tr>
            <td><div align="center">
                <h1><font color="#808db5">マルチプラットフォーム対応<br>並列プログラミングフレームワーク</font></h1>
            </div></td>
          </tr>
          <tr>
            <td><div align="left">
                Yuhi TOMARI
                <script>
                  var date = new Date();
                  var year = date.getFullYear();
                  var month = date.getMonth();
                  var day = date.getDate();
                  
                  var monthList = new Array("January","February","March","April","May","June",
                  "July","August","September","October","November","December");
                  
                  document.write(monthList[month]+" "+day+", "+year);
                  
                </script>
                <hr style="color:#ffcc00;background-color:#ffcc00;text-align:left;border:none;width:300%;height:0.2em;">
            </div></td>
          </tr>
        </table>
      </div>
      
      <div class='slide'>
        <h2>マルチプラットフォームなフレームワークにおける並列プログラミング 1/2</h2>
        <p>
          消費電力や発熱、クロックの限界といった問題から CPU の性能を上げることによる処理性能の向上は難しい。
          マルチコア CPU や GPU を含んだヘテロジニアス構成が主流になっている。
          クロックの性能を上げるのではなく、コア数を増やす事でパフォーマンスを向上させている。
        </p>
        <p>
          マルチコア CPU や GPU といった<font color="red">マルチコアプラットフォーム</font>なアーキテクチャ上で
          リソースを有効活用するには、それぞれのプラットフォームに最適な形でプログラムを並列に動作させる必要がある。
        </p>
        <p>しかしこれらのチューニングは複雑で、コーディング時に毎回行うと複雑さや拡張性の問題がある。</p>
      </div>


      <div class='slide'>
        <h2>マルチプラットフォームなフレームワークにおける並列プログラミング 2/2</h2>
        <p>
          そういった問題を解決するため、本研究では並列プログラミングフレームワーク、 Cerium の開発を行った。
          異なるプラットフォーム上で最適なチューニングを行うため、以下の実装を行った。
        </p>
        <ul>
          <li>パイプライニングによる Task の並列実行</li>
          <li>OpenCL、CUDA を用いた GPGPU 対応</li>
          <li>データ並列実行</li>
          <li>並列処理むけのI/O</li>
        </ul>
        <p>
          Sort、WordCount、FFT といった例題を元に、これら Cerium の並列実行機構が
          マルチプラットフォームにおける並列プログラミングで有効に作用することを示す。
        </p>
      </div>

      <div class='slide'>
        <h2>並列プログラミングフレームワーク Cerium</h2>
        <p>
          Cerium は Linux、MacOSX 上で動作する汎用計算用の並列プログラミングフレームワークである。
        </p>
        <div align="center">
          <img src="./images/cerium_image.png" width="700">
        </div>
        <p>Cerium を用いることでマルチコア CPU と GPU において Scheduling を含めたプログラミングを可能となる。</p>
      </div>

      <div class='slide'>
        <h2>Cerium における Task の生成</h2>
        
        <p>
          Cerium TaskManager では処理の単位を Task としてプログラムを記述していく。
          関数やサブルーチンを Task として扱い、Task に各種パラメタを設定した後に並列実行される。
          Input データの各要素同士を乗算し、 Output に格納する Multiply という例題がある。
          Multiply の例題を元に Cerium で Task が生成される様子を以下に示す。
        </p>
        <pre class="code">
void
multiply_init(TaskManager *manager, float *i_data1,
float *i_data2, float *o_data) {

    // create task
    HTask* multiply = manager->create_task(MULTIPLY_TASK);
    multiply->set_cpu(spe_cpu);

    // set indata
    multiply->set_inData(0, i_data1, sizeof(float) * length);
    multiply->set_inData(1, i_data2, sizeof(float) * length);

    // set outdata
    multiply->set_outData(0, o_data, sizeof(float) * length);

    // set parameter
    multiply−>set_param(0,(long)length);

    // set device
    multiply->set_cpu(SPE_ANY);

    // spawn task
    multiply−>spawn();
}
        </pre>
      </div>
      
      <div class='slide'>
        <h2>Cerium における Task の記述</h2>
        <p>Device 側で実行される Task の記述を示す。</p>
        <pre class="code">
static int
run(SchedTask ∗s) {
    float ∗i_data1 = (float∗)s−>get_input(0); // get input
    float ∗i_data2 = (float∗)s−>get_input(1); // get output
    float ∗o_data  = (float∗)s−>get_output(0); // get parameter
    long length    = (long)s−>get_param(0);

    // calculate
    for (int i=0; i&lt;length; i++) {
        o_data[i] = i_data1[i] ∗ i_data2[i];
    }
    return 0; 
}
        </pre>
        <p>Host 側では Task を生成する際に様々なパラメタを設定しており、
          Task にはそれを取得する API が用意されている。</p>
        <table border="0" >
          <tr bgcolor="palegreen">
            <th align="center">API</th><th align="center">content</th>
          </tr>

          <tr bgcolor="dbffa3">
            <th align="left" >get_input</th><th align="left">入力データのアドレスを取得</th>
          </tr>
          <tr bgcolor="palegreen">
            <th align="left">get_output</th><th align="left">出力先データのアドレスを取得</th>
          </tr>
          <tr bgcolor="dbffa3">
            <th align="left">get_param</th><th align="left">パラメータを取得</th>
          </tr>
        </table>
      </div>

      <div class='slide'>
        <h2>Task の依存関係の記述</h2>
        <p>
          並列処理を行う場合、Task を大量に生成する場合がある。
          そういった場合において一括で Task を生成/実行してしまうと並列度が落ちてしまう。
          これは生成しただけで Task そのものがメモリを圧迫してしまっていることが原因となる。
        </p>
        <p>
          そういった 例題において、Task は一定数ずつ徐々に生成/実行する必要がある。
          ということは、Block 間で依存関係を設定する必要がある。
          依存関係について Cerium の Bitonic Sort を例題に考える。
        </p>
      </div>


      <div class='slide'>
        <h2>Bitonic Sort の例題</h2>
        <p>Bitonic Sort は配列の分割を行い、分割した部分に対して Sort を行う。
          分割後の Sort には QuickSort を使用している。Bitonic Sort は2つの Sort を行う。
        </p>
        <ul>
          <li>使用する CPU 数を元に分割数を算出し、分割した箇所に対して Sort する(fsort)
          <li>Block の中間から次の Block の中間までを Sort する(bsort)
        </ul>
        <p>この2つの Sort を分割数分繰り返している</p>
      </div>

      <div class='slide'>
        <h2>Bitonic Sort の例題</h2>
        <div align="center">
          <img src="./images/fsort_bsort.png" width="850">
        </div>
      </div>

      <div class='slide'>
        <h2>Task 間の依存関係</h2>
        <p>Bitonic Sort を行う際、依存関係として bsort は fsort の結果に対して sort を行い、
          fsort は前の Stage の bsort に対して Sort を行う必要がある
        </p>
        <p>よって、BitonicSort のような大量に Task を生成する例題を並列実行する場合、
          「例題の性質としての依存関係」と「Task を徐々に生成するための依存関係」
          の二種類の依存関係を記述する必要がある。</p>
      </div>

      <div class='slide'>
        <h2>依存関係の記述</h2>
        <p>例題独自の依存関係</p>
              <pre class="code" align="left">static int
sort_start(SchedTask *manager, void *d, void *e)
{
    Sort *s =  (Sort*)manager->get_param(0);
    long half_num = s->split_num-1;

    for (int i = 0; i < s->split_num-1; i++) {
        s->fsort[i] = manager->create_task(QUICK_SORT,(memaddr)&s->data[i*block_num],
                                           sizeof(Data)*block_num,
                                           (memaddr)&s->data[i*block_num],
                                           sizeof(Data)*block_num);

            s->fsort[i]->wait_for(s->bsort[i-1]);
    }
~省略~
              </pre>
     </div>
      <div class='slide'>
        <h2>依存関係の記述</h2>
        <p>Task を徐々に生成するための依存関係</p>
        <pre class="code" align="left">
    // recursive Task
    HTaskPtr restart = manager->create_task(SortSimple,0,0,0,0);
    restart->set_param(0,(memaddr)s);
    restart->wait_for(s->fsort[0]);
    for (int i = 0; i < s->split_num; i++) {
        s->fsort[i]->spawn();
    }
    restart->spawn();
    return 0;
}
        </pre>
     </div>

      <div class='slide'>
        <h2>TaskManager の構成</h2>
        <div align="center">
          <img src='images/createtask.png' width="700">
        </div>
        <ul>
          <li>TaskManagerと各Threadsの間には Syncronized な Mail Queueがある。
          <li>依存関係の解決された Task は TaskManager から Mail Queue に送られる。
          <li>Task に設定された CPUType に対応した Threads が Mail Queue から Task を取得し、並列実行していく。
        </ul>
      </div>

      <div class='slide'>
        <h2>マルチコア CPU 上での並列実行</h2>
        <div align="center">
          <img src="images/pipeline.png" width="600">
        </div>
        <p>
          Cerium は Cell 上で動作するフレームワークであったが MacOSX、Linux 上での並列実行に対応させた。
        </p>
        <p>
          マルチコア CPU 上での並列実行は、Synchronized Queue とパイプラインによって実現されている。
          TaskManager で依存関係を解決された Task は Scheduler に送信され、
          Scheduler が持っているパイプラインの機構に沿って並列に実行する。
        </p>
      </div>

      <div class='slide'>
        <h2>マルチコア CPU におけるパイプラインの実装</h2>
        <table>
          <tr>
            <th>
              <pre class="code" align="left">void
Scheduler::run(SchedTaskBase* task1) {
    SchedTaskBase* task2 = new SchedNop();
    SchedTaskBase* task3 = new SchedNop();

    // main loop
    do {

        task1->read();
        task2->exec();
        task3->write();

        delete task3;

        task3 = task2;
        task2 = task1;
        task1 = task1->next(this, 0);

    } while (task1);

    delete task3;
    delete task2;
}</pre>
            </th>
            <th  align="left">
              <p>
                Cerium の Task は SchedTask と呼ばれるデータ構造で表現されている。
                SchedTask は read/exec/write のメソッドを持っており、
                パイプラインの各ステージで段階的に実行される。
              </p>
              <p>
                引数として TaskList を受け取り、List 内の Task をパイプライン実行する。
                task3 が write を担当しており、write が終わった Task は終了となる。
              </p>
              <p>
                終了した task は delete して良い。
                task3=task2、task2=task1 と SchedTask をずらして行き、TaskList から 次の Task を読み込む。
              </p>
            </th>
          </tr>
        </table>
      </div>
      
      <div class='slide'>
        <h2>マルチコア CPU におけるデータ並列</h2>
        <p>
          Cerium はタスク並列による実行のみを行っていた。
          並列化を行う問題によってはデータ並列を行った方が良い場合がある。
        </p> 
        <p>
          タスク並列は1つのデータに対して異なる処理方法を適用し、それぞれ独立して実行させるものである。
        </p>

        <p>
          データ並列は多くのデータを1つのタスクに与え、データごとに独立した処理を行わせる手法である。
        </p>
        <p>処理対象となるデータが充分な数のサブセットへ分割可能な場合、データ並列が有効となる。</p>
      </div>

      <div class='slide'>
        <h2>iterate API</h2>
        <p>
          データ並列による実行を行う場合、一つの記述から複数のTaskを生成する必要がある。
          生成した各TaskにIDとinput/output dataを割り当てる「iterate」というAPIを実装した。
        </p>

        <table>
          <tr>
            <td>
              <img src="images/iterate.png" height="450"></img>
            </td>
            <td>
              <ul>
                <li>1つの記述から複数のTaskを生成する</li>
                <li>生成した複数のTaskにIDとInput/Output Dataを割り当てる</li>
              </ul>
                この例だと、Taskの持つidとTaskに割り当てられるデータは
                1対1で対応している。id=割り当てられたdataのindexとなっている。<br><br>
              並列プログラミングだと、並列化部分が全て同一の Task であるという場合は少なくない。
              iterate API ならループで回すような処理をする必要が無く、容易な Syntax で記述できる。
            </td>
        </tr></table>
      </div>

      <div class='slide'>
        <h2>マルチコア CPU によるデータ並列実行</h2>
        <p>
          マルチコア CPU においてデータ並列実行する場合、以下のように記述する。
          例題として 2つの input のデータの積を output データに格納して返す例題、multiply を用いた。
        </p>
        <pre class="code">
static int 
run(SchedTask *s, void *rbuf, void *wbuf) {
    float *indata1, *indata2, *outdata;

    indata1 = (float*)s->get_input(rbuf, 0);
    indata2 = (float*)s->get_input(rbuf, 0);
    outdata = (float*)s->get_output(wbuf, 0);

    long id = (long)s->get_param(0);
    outdata[id] = indata1[id] * indata2[id];
    return 0;
}
        </pre>
      <p>get_param によって自分の担当する index を取得し、担当範囲のみを計算する。</p>
      <p>データ並列実行する場合、各Task に Input/Outpu を設定するのではなく、
        全ての Task でデータを共有する。共有したデータの自分の担当する箇所にのみ計算を行う。
        そのため少ないコピーにおさえることができる。
      </p>
      </div>

    
      <div class='slide'>
        <h2>DMA 転送</h2>
        <p>Cerium は DMA 転送をサポートしている。
          DMA とは CPU を介さずに周辺装置とメモリ間でデータ転送を行う転送方式である。
        </p>
        <p>
          DMA は prefetch と呼ばれる転送先読みの機能がある。
          DMA の転送効率を向上させるために送信データを予め取り込んでおく機能である。
          prefetch による転送機能を追加した。
        </p>
      </div>

      <div class='slide'>
        <h2>GPU 上での並列実行</h2>
        <p>
          GPU 上での並列実行をサポートするフレームワークとして、OpenCL と CUDA が挙げられる。
          これらのフレームワークを用いて Cerium に GPU 上で 並列実行する機能を加えた。
        </p>
        <p>
          TaskManager から受け取った Task やデータをOpenCL、CUDA の API を介して GPU に転送する機構、
          GpuScheduler と CudaScheduler を実装した。
        </p>
        <div align="center">
          <img src="./images/gpu_image.png" width="600">
        </div>
      </div>
      
      <div class='slide'>
        <h2>フレームワークを用いた GPU の制御</h2>
        <p>
          GpuScheduler、CudaScheduler ではそれぞれのフレームワークを用いて GPU の制御を行っている。
          行われていることは以下の3つに分けられる。
        </p>
        <ul>
          <li>Host から Device へのデータ転送
          <li>kernel の実行
          <li>Device から Host へのデータ転送
        </ul>
        <p>
           CommandQueue と呼ばれる機構を用いてこういった GPU を制御するための処理を行っていく。
           CommandQueue に命令を起こるためのしくみで、制御は全てこの Queue を介して行われる。
        </p>
        <p>これらはRead, Exec、Write に対応する。
          GPGPU 用の Scheduler でもパイプラインを構成する。</p>
      </div>

      <div class='slide'>
        <h2>GPGPU におけるパイプラインの実装(Read)</h2>
        <p>
          GpuScheduler では SchedTask を用いてない。
          メインループでは2つの CommandQueue を保持し、GPU の制御命令を二段のパイプラインで実行していく。
          TaskList から Task を取り出し、Task から実行する kernel やパラメタを生成し、
          各種フレームワークの API を通して GPU のメモリに転送される。
        </p>

        <p>
          全ての Task が終了すると SynchronizedQueue を通してTaskManager に終了を通知する。
        </p>

        <p>
          Scheduler の内部で Platform や DeviceID の取得、
          kernel の build や load といった API を使用するための初期化も行っており、
          並列化したい処理のみに集中できる。
        </p>
      </div>

      <div class='slide'>
        <h2>GPGPU におけるデータ並列</h2>
        <p>
          マルチコア CPU と同様に、GPGPU に関してもデータ並列実行をサポートした。
        </p>
        <p>
          GPU 上でデータ並列実行する際も iterate API によりデータ並列用の Task を生成できる。
          生成部分の記述はマルチコア CPU と同じ形式で記述できる。
          また、Task 自体の記述もほぼ同じ形式となる。以下に Task の例を示す。
        </p>

        <table>
          <tr align="left">
            <th>
              <pre class="code">
__kernel void // OpenCL
multiply(__global const long  *params,
         __global const float *input1,
         __global const float *input2,
         __global const float *output) {

    long id = get_global_id(0);

    output[id] = input1[id] * input2[id];
}
              </pre>
            </th>
            <th>
              <pre class="code">
__global__ void // CUDA
multiply(__global const long  *params,
         __global const float *input1,
         __global const float *input2,
         __global const float *output) {

    int id = blockIdx.x * blockDim.x + threadIdx.x;

    output[id] = input1[id] * input2[id];
}
              </pre>
            </th>
          </tr>
        </table>
      </div>

      <div class='slide'>
        <h2>並列処理向け I/O</h2>
        <p>
          ファイルの読み込みなどの I/O を含むプログラムは、
          読み込み時間が Task のと比較してオーバーヘッドになることが多い。
          プログラムの並列化を行ったとしても I/O がボトルネックになってしまうと処理は高速にならない。
        </p>
        <p>並列計算と同時に動作する、並列 I/O の実装を行った。</p>
      </div>

      <div class='slide'>
        <h2>Cerium の I/O(mmap による読み込み)</h2>
        <p>
          Cerium ではファイルの読み込みを mmap で行っていた。</p>
          <ul>
            <li>mmap はまず仮想メモリにファイルをマッピングする。
            <li>マッピングしたメモリ空間にアクセスがあったら OS が読み込みを行う。
            <li>mmap は並列に動作せず、逐次処理
            <li>読み込みが OS 依存となり、環境に左右されやすい
        <p>並列に動作する I/O の機構が必要である</p>
      </div>

      
      <div class='slide'>
        <h2>WordCount</h2>
        <p>サイズの大きいファイルを読み込む例題、WordCount を元に並列 I/O について考える。</p>
        <p>
          WordCount は Input としてファイルを受け取り、ファイルの単語数と行数を集計して表示する例題である。
        </p>
        <table>
          <tr><th><img src="./images/wordcount.png" width="600"></th>
            <th align="left">
              <ul>
                <li>input ファイルを一定の大きさ分割する
                <li>読み込んだテキストファイルに対してそれぞれ並列に計算を行う
                <li>PrintTask が計算結果を集計して出力する
              </ul>
            </th>
          </tr>
        </table>
      </div>
      
      <div class='slide'>
        <h2>BlockedRead による I/O の並列化</h2>
        <p>ファイルを読み込んで、読み込んだファイルに対して並列実行を行う場合、ファイルを分割して処理を行う。</p>
        <p>よって読み込みの処理自体を分割し、ある程度の大きさ(Block)ごとに読み込みと Task の実行を行う。</p>
        <p>読み込みの処理自体を分割して行う。これを BlockedRead と呼ぶ。</p>
        </p>
      </div>

      <div class='slide'>
        <h2>BlockedRead を用いた WordCount</h2>
        <div align="center">
          <img src="./images/blockedread.png" width="600">
        </div>
        <p>
          BlockedRead を用いて WordCount を行う際、読み込み用の Task と
          読み込んだファイルに対して処理を行う Task の2つを生成する。
        </p>
        <p>ファイルを分割して読み込み、
          読み込んだファイルに対して WordCount を行う一定数のTask(BlockedTask)を割り当てる。
          Task には依存関係を設定する必要があり、図のTask n+1 はTask nを待つ必要がある。
        </p>
        <p>まだ読み込みが終了していない領域に割り当てられた Task が起動してしまう事を防ぐためである。</p>
        <p>この wait によるロックはオーバーヘッドとなるため、なるべく発生しないことが望ましい。</p>
      </div>

      <div class='slide'>
        <h2>I/O 専用のThread</h2>
        <p>
          BlockedRead の依存関係による wait はなるべく発生しないことが望ましい。
          そのため、BlockedRead は連続で Task の起動を行う必要がある。
        </p>
        <p>
          Cerium には SPE_ANY という Thread があり、この Thread で Task の実行を行うと自動で実行するコアを割り振る。
          しかし、SPE_ANY で BlockedRead を実行すると BlockedRead 間に別の Task が割り込んでしまう場合がある。
        </p>
        <div align="center">
          <img src="./images/speblockedread.png" width="700">
        </div>
        <p>TaskBlock の依存関係によっては wait がかかってしまう。そこで、I/O 専用の Thread を作成した。</p>
      </div>

      <div class='slide'>
        <h2>I/O 専用のThread</h2>
        <p>
          IO 専用の Thread を作成したが、それだけでは問題は解決しない場合がある。
          IO thread 内では割り込みが生じる可能性はないが、thread レベルで割り込みが起きる可能性がある。
          IO thread-SPE_ANY-IO Thread のような実行順序となる場合である。
        </p>
        <div align="center">
          <img src="./images/iothread.png" width="700">
        </div>
        <p>
          そのため、pthread_getschedparam() という POSIX スレッドの API を用いて IO Thread の priority を高く設定した。
          IO Thread は必ず連続で行われることになる。
        </p>
      </div>

      <div class='slide'>
        <h2>-</h2>
      </div>

      <div class='slide'>
        <h2>実験に利用する例題-WordCount-</h2>
      </div>
      
      <div class='slide'>
        <h2>実験に利用する例題-FFT-</h2>
      </div>

      <div class='slide'>
        <h2>実験環境</h2>
      </div>

      <div class='slide'>
        <h2>マルチコア CPU による並列実行のベンチマーク</h2>
      </div>

      <div class='slide'>
        <h2>DMA の prefecth に関するベンチマーク </h2>
      </div>

      <div class='slide'>
        <h2>GPGPU のベンチマーク</h2>
      </div>

      <div class='slide'>
        <h2>データ並列実行のベンチマーク</h2>
      </div>

      <div class='slide'>
        <h2>GPGPU のベンチマーク</h2>
      </div>
      
      <div class='slide'>
        <h2>FFT による GPGPU のベンチマーク</h2>
      </div>

      <div class='slide'>
        <h2>BlockedRead による並列 I/O のベンチマーク</h2>
      </div>

      <div class='slide'>
        <h2>まとめ</h2>
      </div>

      <div class='slide'>
        <h2>今後の課題</h2>
      </div>

    </div> <!-- presentation -->
  </bodypp>
</html>