view slide/thesis.pdf.html @ 28:7174f22ed695

tweak
author ichikitakahiro <e165713@ie.u-ryukyu.ac.jp>
date Thu, 10 Feb 2022 23:55:41 +0900
parents 3f39907150c5
children bca6c79006cf
line wrap: on
line source






<!DOCTYPE html>
<html>
<head>
   <meta http-equiv="content-type" content="text/html;charset=utf-8">
   <title>GearsOSの分散ファイルシステムの設計</title>

   <meta name="generator" content="Slide Show (S9) v4.1.0 on Ruby 2.6.8 (2021-07-07) [universal.x86_64-darwin21]">
   <meta name="author"    content="Takahiro Ikki, Shinji Kono" >

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

<!-- JS -->
<script src="s6/js/jquery-1.11.3.min.js"></script>
<script src="s6/js/jquery.slideshow.js"></script>
<script src="s6/js/jquery.slideshow.counter.js"></script>
<script src="s6/js/jquery.slideshow.controls.js"></script>
<script src="s6/js/jquery.slideshow.footer.js"></script>
<script src="s6/js/jquery.slideshow.autoplay.js"></script>

<!-- prettify -->
<link rel="stylesheet" href="scripts/prettify.css">
<script src="scripts/prettify.js"></script>

<style>
  .slide {page-break-after: always;}
</style>




</head>
<body>

<div class="layout">
  <div id="header"></div>
  <div id="footer">
    <div align="right">
      <img src="s6/images/logo.svg" width="200px">
    </div>
  </div>
</div>

<div class="presentation">

  <div class='slide cover'>
    <table width="90%" height="90%" border="0" align="center">
      <tr>
        <td>
          <div align="center">
              <h1><font color="#808db5">GearsOSの分散ファイルシステムの設計</font></h1>
          </div>
        </td>
      </tr>
      <tr>
        <td>
          <div align="left">
               Takahiro Ikki, Shinji Kono
               琉球大学理工学研究科情報工学専攻
            <hr style="color:#ffcc00;background-color:#ffcc00;text-align:left;border:none;width:100%;height:0.2em;">
          </div>
        </td>
      </tr>
    </table>
  </div>


<div class='slide'>
  
<!-- _S9SLIDE_ -->
<h2 id="gearsosのファイルシステムの設計と実装">GearsOSのファイルシステムの設計と実装</h2>
<ul>
  <li>DataGearとCodeGearという単位を用いるOS</li>
  <li>従来のファイルシステムには型とTransactionが無い</li>
  <li>DataGear単位のTransactionとしてファイルシステムを設計</li>
  <li>APIとしてTake/Put/Peekを採用した</li>
  <li>通信としてもDBアクセスとしても使える(メモリからSSDへの通信に見える)</li>
  <li>本研究ではsocket baseな通信とWordCountの例題を作成した</li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="gearsosの基本単位">GearsOSの基本単位</h2>
<ul>
  <li>CodeGear
    <ul>
      <li>実行Codeの単位</li>
      <li>入力DataGearと出力DataGearを持つ</li>
      <li>goto文(jump命令)を使って遷移する</li>
      <li>実行単位は途中で割り込まれたりしない(Atmocity)</li>
    </ul>
  </li>
  <li>DataGear
    <ul>
      <li>Cの構造体に相当する</li>
      <li>ノーマルレベルでは変更されない(関数型プログラミング)</li>
    </ul>
  </li>
  <li>C言語を拡張する形でCbC言語により実装される(gcc/llvm)</li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="codegearとdatagear">CodeGearとDataGear</h2>
<ul>
  <li>InputDataGearを受け取って、CodeGearが処理し、OutputDataGearを出力する</li>
  <li>OutputDataGearは次のCodeGearのInputDataGearとなる</li>
  <li>ファイルシステムではDataGearをkeyで待ち合わせる</li>
</ul>
<div style="text-align: center;">
   <img src="images/cg-dg.pdf" alt="cgdgの関係図" width="600" />
</div>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="gearsosのinterface">GearsOSのInterface</h2>
<ul>
  <li>JavaのInterfaceに相当する</li>
  <li>APIとなるCodeGearの名前と型を書く(__next(…)が継続)</li>
  <li>引数渡しの構造体として使う(引数はすべてここに定義される必要がある)
    <pre><code>typedef struct Tree&lt;&gt;{
  union Data* tree;
  struct Node* node;
  __code put(Impl* tree,Type* node, __code next(...));
  __code get(Impl* tree, Type* node, __code next(...));
  __code remove(Impl* tree,Type* node, __code next(...));
  __code next(...);
} Tree;
</code></pre>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="interfaceの呼び出し">Interfaceの呼び出し</h2>
<ul>
  <li>createで作成する(通常の関数呼び出し)</li>
  <li>DataGearとして作成する場合はnewを使う</li>
  <li>gotoでputAPIを呼び出す(nextは継続)</li>
  <li>InterfaceなどのDataGearはプロセスに相当するContextにすべて格納される
    <pre><code>struct Queue* queue = createSychronizedQueue(context);
struct Task* task = new Task();
goto queue-&gt;put(task, next(...));
</code></pre>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="interfaceの実装">Interfaceの実装</h2>
<ul>
  <li>Interfaceの実装に使われるデータ構造を記述するImplementがある</li>
  <li>実装で使われるDataGearを記述する(ヒープに確保される)</li>
  <li>create時にAPIを実装するCodeGearをInterfaceの構造体に代入される
    <pre><code>typedef struct SynchronizedQueue &lt;&gt; impl Queue {
struct Element* top;
struct Element* last;
struct Atomic* atomic;
} SynchronizedQueue;
</code></pre>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="codegearとdatagearにはメタレベルなものが存在する">CodeGearとDataGearにはメタレベルなものが存在する</h2>
<ul>
  <li>メタレベルな記述はトランスコンパイラにより自動生成される(記述することも可能)</li>
  <li>CodeGearの前後にMetaなCodeGearが挿入される</li>
</ul>
<div style="text-align: center;">
 <img src="images/meta-cg-dg.pdf" alt="ノーマルレベルとメタレベルの視点からのGearの関係" width="800" />
</div>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="stubcodegearとgoto-meta">stubCodeGearとgoto meta</h2>
<ul>
  <li>ContextからInputDataGearを取り出す(stubCodeGear)</li>
  <li>OutputDataGearをContextに書き込み、次のCodeGearを呼び出す(goto meta)</li>
  <li>stubCodeGear/goto metaは変更可能(メタプログラミング)</li>
</ul>
<div style="text-align: center;">
 <img src="images/meta-cg-dg.pdf" alt="ノーマルレベルとメタレベルの視点からのGearの関係" width="800" />
</div>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="gearsosのファイルシステムの設計">GearsOSのファイルシステムの設計</h2>
<ul>
  <li>DataGearの単位でデータを操作したい</li>
  <li>通信データに対応した複数のストリームを持つ</li>
  <li>Transactionとしてatomicに操作したい
    <ul>
      <li>従来のファイルシステムはTransactionはUserレベルで実装される</li>
    </ul>
  </li>
  <li>ファイル操作と通信を同じAPIで実現する
    <ul>
      <li>ChristieのDataGearManagerを参考にする</li>
      <li>Take/Put/Peek</li>
    </ul>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="takeputpeek">Take/Put/Peek</h2>
<ul>
  <li>ファイルはQueueで構成される</li>
  <li>putでQueueに追加</li>
  <li>takeでQueueからの取り出し</li>
  <li>peekでQueueから取り出さない読み出し</li>
</ul>
<div style="text-align: center;">
   <img src="images/QueueElement.pdf" alt="Queueの構造" width="800" />
</div>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="gearsfsのトランザクション">GearsFSのトランザクション</h2>
<ul>
  <li>GearsOSのCodeGear操作はatomicなので割り込まれない
    <ul>
      <li>atomicityはOSが保証する</li>
      <li>これによりTake/Put/PeekがTransactionであることを保証する</li>
    </ul>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="queueによるgearsosのファイル">QueueによるGearsOSのファイル</h2>
<ul>
  <li>GearsOSのファイルはDataGearを保持するQueueとなる</li>
  <li>オンメモリのファイルに相当する</li>
  <li>Queueをデバイスにcopyして持続性を実現する</li>
  <li>書き込み先はDataGearManagerで選択する</li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="putのimplementation">PutのImplementation</h2>
<ul>
  <li>QueueのElementをnewで作成する</li>
  <li>Queueのリンクを構築する</li>
  <li>継続nextに跳ぶ
    <pre><code>__code putSingleLinkedQueue(struct SingleLinkedQueue* queue, union Data* data, __code next(...)) {
  Element* element = new Element();
  element-&gt;data = data;
  element-&gt;next = NULL;
  queue-&gt;last-&gt;next  = element;
  queue-&gt;last = element;
  goto next(...);
}
</code></pre>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="takeのimplementation">TakeのImplementation</h2>
<ul>
  <li>QueueからElement経由でDataGearを取り出す</li>
  <li>Queueのリンクを修正し、nextでデータを引き渡す</li>
  <li>Elementには任意の型のDataGearが格納されている
    <pre><code>__code takeSingleLinkedQueue(struct SingleLinkedQueue* queue, __code next(union Data* data, ...)) {
  printf("take\n");
  struct Element* top = queue-&gt;top;
  struct Element* nextElement = top-&gt;next;
  if (queue-&gt;top == queue-&gt;last) {
      data = NULL;
  } else {
      queue-&gt;top = nextElement;
      data = nextElement-&gt;data;
  }
  goto next(data, ...);
}
</code></pre>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="datagearmanager">DataGearManager</h2>
<ul>
  <li>Take/Put/PeekはDataGearManagerに対して行う</li>
  <li>メモリ上のQueueはLocalDGMになる</li>
  <li>RemoteDGMは他のノードやプロセスあるいはストレージデバイスをあらわす</li>
  <li>一つのDataGearManager上に複数のQueueがあり、keyで識別する</li>
  <li>RemoteDGMに書き込むと相手のLocalDGMに書き込まれる</li>
  <li>Take/Peekは書き込みを待ち合わせる</li>
  <li>複数のTakeを待ち合わせることができる</li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="datagearmanagerによる通信構成">DataGearManagerによる通信構成</h2>
<ul>
  <li>任意の相手のRemoteDGMを作成することでTopologyが形成される</li>
  <li>手元のRemoteDGMに書き込むと相手のLocalDGMに書き込まれる</li>
  <li>RemoteDGMはproxyとして動作する</li>
  <li>この構成は分散フレームワークChristie(当研究室作成)と同じ</li>
</ul>
<div style="text-align: center;">
 <img src="images/Remote_DataGearManager.pdf" alt="RemoteDGMの関係図" width="800" />
</div>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="socket通信によるremotedgmの実装">socket通信によるRemoteDGMの実装</h2>
<ul>
  <li>GearsOSはUnix上の言語フレームワークとして実装されている</li>
  <li>Unixのsocket通信を使ってQueueのputを実装する</li>
  <li>proxy側はQueueにputされたDataをsocketで送信する</li>
  <li>送信されたDataはLocal側でgetDataAPIで取り出される</li>
  <li>send/recvはUnixのAPI
    <pre><code>__code sendDataRemoteDGMQueue(struct RemoteDGMQueue* cQueue, union Data* data, __code next(...), __code whenError(...)){
  char recv_buf;
  int send_size, recv_size;

  send_size = send(cQueue-&gt;socket, data, sizeof(union Data), 0);
  recv_size = recv(cQueue-&gt;socket, &amp;recv_buf, 1, 0);
  //error処理は省略
  goto next(...);
}
</code></pre>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="受信側の実装">受信側の実装</h2>
<ul>
  <li>ファイル本体(Local側)はsocketからDataを取り出す</li>
  <li>取り出されたデータはQueueにputされる
    <pre><code>__code getDataLocalDGMQueue(struct LocalDGMQueue* cQueue, __code next(...), __code whenError(...)){
  int recv_size, send_size;
  char send_buf;

  union Data* recv_data;
  recv_size = recv(cQueue-&gt;socket, recv_data, sizeof(union Data), 0);

  //error処理は省略
  Gearef(context, cQueue)-&gt;data = recv_data;
  goto putLocalDGMQueue(recv_data, next);
}
</code></pre>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="複数のストリームから構成されるファイル">複数のストリームから構成されるファイル</h2>
<ul>
  <li>入力されるデータに応じた個別のstreamを備えたい
    <ul>
      <li>例えばUSBは複数のチャネルを持つ</li>
      <li>メタデータの取り出しは別streamになる</li>
      <li>通信として使う場合に複数のプロトコルがある方が良い(FTP)</li>
    </ul>
  </li>
  <li>Streamはkey nameを持ち、keyでアクセスを行う</li>
  <li>赤黒木を用いる</li>
  <li>DataのTake/Put時には必ずkey nameの指定が必要となる</li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="socketを使ったremotedgm">socketを使ったRemoteDGM</h2>
<ul>
  <li>RemoteDGMに書き込みが行われるとsocketで通信が起きる</li>
  <li>受信側はLocalDGMにDataGearを書き込む</li>
</ul>
<div style="text-align: center;">
   <img src="images/socketCom.pdf" alt="socketを通じたレコード送信" width="800" />
</div>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="wordcountの例題">wordCountの例題</h2>
<ul>
  <li>ファイル内の文字列を1行づつ受け取り、文字列,行数をカウントする例題</li>
  <li>文字列送信側とCount側を別ノード上で行うことで、ファイルの呼び出しと通信処理が構成できる</li>
  <li>RemoteDGMへの書き込みで通信する</li>
  <li>acknowredgeを逆方向のRemoteDGMによる通信で実現する(現在は直接送信)</li>
</ul>
<div style="text-align: center;">
 <img src="images/slideGearsWC.pdf" alt="リモートDGMによるWordCount" width="800" />
</div>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="gearsfile-apiによるwordcount13">GearsFile APIによるWordCount(1/3)</h2>
<ul>
  <li>FileOpen側(NodeA)とWordCount側(NodeB)でノードが別れる</li>
  <li>(手順1)FileOpen側はFilePloxyにDataRecordをputする</li>
  <li>(手順2)WordCount側は処理の後、ackを返信する</li>
</ul>
<div style="text-align: center;">
   <img src="images/slideGearsWC.pdf" alt="ChristieAPIによるWordCount" width="800" />
</div>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="gearsfile-apiによるwordcount23">GearsFile APIによるWordCount(2/3)</h2>
<ul>
  <li>(手順3)1,2をループし、FileOpen側はEoFならフラグを送信する</li>
</ul>
<div style="text-align: center;">
   <img src="images/slideGearsWC.pdf" alt="ChristieAPIによるWordCount" width="800" />
</div>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="gearsfile-apiによるwordcount33">GearsFile APIによるWordCount(3/3)</h2>
<ul>
  <li>(手順4)EoFを受信したWordCountは結果を返信し、双方の処理を終了させる</li>
</ul>
<div style="text-align: center;">
   <img src="images/slideGearsWC.pdf" alt="ChristieAPIによるWordCount" width="800" />
</div>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="現在のgearsfile-apiの開発状況">現在のGearsFile APIの開発状況</h2>
<ul>
  <li>実装ずみ
    <ul>
      <li>keyアクセスに対応したファイル通信
        <ul>
          <li>単一のQueueによる通信の記述</li>
        </ul>
      </li>
      <li>リストとなるTree
        <ul>
          <li>赤黒木</li>
        </ul>
      </li>
      <li>atomicな操作が行えるQueue
        <ul>
          <li>複数からのアクセス時にデータ整合を保つ</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>実装中
    <ul>
      <li>keyアクセスが行えるQueueのリスト</li>
      <li>リスト単位での通信の記述</li>
    </ul>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="結論">結論</h2>
<ul>
  <li>GearsOSのファイルの設計を行った
    <ul>
      <li>ファイルの構造の設計
        <ul>
          <li>DataGear単位での操作が行える</li>
        </ul>
      </li>
      <li>socketによる通信部分の実装
        <ul>
          <li>GearsOS上でのソケット通信の記述</li>
        </ul>
      </li>
      <li>APIの段階的な設計記述
        <ul>
          <li>Proxyによるファイル通信</li>
        </ul>
      </li>
      <li>GearsOSの調査</li>
    </ul>
  </li>
  <li>Streamのリスト単位での通信の完成</li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="将来的な課題">将来的な課題</h2>
<ul>
  <li>TopoplogyManagerの設計
    <ul>
      <li>参加したノードを任意の形のTopologyに接続する機能</li>
      <li>ファイルシステム向けの機能を追加したい
        <ul>
          <li>DNS</li>
          <li>中枢としてのTopologyのノード監視</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>Securityシステムの追加
    <ul>
      <li>証明書などによるファイル操作制限</li>
      <li>不正な分散ファイルシステムへのアクセス</li>
    </ul>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="gearsosの生成形の問題点">GearsOSの生成形の問題点</h2>
<ul>
  <li>GearsOSのメタレベルの処理の記述はトランスコンパイラにより行われる</li>
  <li>場合によりメタレベルの記述を行わなくてはならない
    <ul>
      <li>他のInterfaceを継承したオブジェクトからのDataGear継承
        <ul>
          <li>例)Queueからのデータ取り出し</li>
          <li>トランスコンパイラはどのInterfaceに記述されたDataGearを参照するべきか判断が難しい</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<pre><code>__code Task2(TQueue* localDGMQueue){
    goto localDGMQueue-&gt;take(Task3);
}

__code Task3(TQueue* localDGMQueue, FileString* string){
    printf("take[%s] [num:%d]\n", string-&gt;str, string-&gt;size);
    goto getData();
}

//プログラマが実装したいstub
__code Task3_stub(struct Context* context){
    TQueue* localDGMQueue = (struct TQueue*)Gearef(context, TQueue)-&gt;tQueue;
    FileString* string = Gearef(context, TQueue)-&gt;data;
    goto Task3(context, localDGMQueue, string);
}

//自動生成されたErrorなstub
__code Task3_stub(struct Context* context) {
	TQueue* localDGMQueue = Gearef(context, TQueue);
	FileString* string = Gearef(context, FileString);
	goto Task3(context, localDGMQueue, string);
}
</code></pre>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="並列処理構文par-gotoが持つ問題">並列処理構文par gotoが持つ問題</h2>
<ul>
  <li>par gotoとはGearsOSに実装された並列処理構文である</li>
  <li>StreamQueueに対するput/takeの並列処理の実装をpar goto構文で試みた</li>
  <li>par gotoはトランスコンパイラへの依存性が高い
    <ul>
      <li>stubCodeGearのように任意な書き換えが行えない</li>
    </ul>
  </li>
  <li>特定のCodeGearの宣言のみでしか正常な処理が生成されない
    <ul>
      <li>Interfaceに記述されたAPICodeGearではバグが生じる</li>
      <li>par gotoでの使用を前提としたCodeGearを書かなくてはならない</li>
    </ul>
  </li>
  <li>処理が重いという問題点も存在する</li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="以下返答用">以下返答用</h2>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="takeputpeek-1">Take/Put/Peek</h2>
<ul>
  <li>PeekはReadOnly (最新の設定を読みこむなど)</li>
  <li>Take/PutはDGが一つならUpDataに相当する</li>
  <li>書き込みが単一スレッドなら順序は保証される</li>
  <li>書き込みが複数の場合、Putの順序は保証されない</li>
  <li>データベースとの違い
    <ul>
      <li>putがQueueとして蓄積される</li>
      <li>Keyが一つしかない(通信路として使える)</li>
    </ul>
  </li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="__code-nextint-ret-の意味">__code next(int ret, …)の意味</h2>
<ul>
  <li>軽量継続を表す</li>
  <li>nextは引数として渡されたCodeGear</li>
  <li>int ret は返す値</li>
  <li>…は軽量継続の呼び出された時の値渡しのInterface</li>
  <li>一段の呼び出しStackのような役割になる</li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="codegearと再帰呼び出し">CodeGearと再帰呼び出し</h2>
<ul>
  <li>再起呼び出ししなければ関数呼び出し的に使える(末尾再起)</li>
  <li>再帰呼び出ししたい場合、明示的に自分でStackを作る</li>
  <li>…はContextにすべて置かれている</li>
  <li>Processはすべて異なるContextを持っている</li>
  <li>Context自体は共有されない</li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="datagearの型">DataGearの型</h2>
<ul>
  <li>union Data は一つのプロセス(Context)で使われるすべてのDataGearのUnion</li>
  <li>メタ部分に型に対応する番号を持っている</li>
  <li>番号を使って型を識別することができる</li>
  <li>任意の型を格納できるQueueやStackを作成することが可能</li>
  <li>メタレベルではunion Dataを使ってDataGearの詳細に立ち入らず処理できる</li>
</ul>



</div>

<div class='slide'>
  <!-- _S9SLIDE_ -->
<h2 id="remotedgmとacknowledge">RemoteDGMとacknowledge</h2>
<ul>
  <li>Take/Put/Peekのコマンドは TCP上でacknowledgeを使って通信されている</li>
  <li>これとは別に自分と相手のCodeGearどうしのacknowledgeが必要</li>
  <li>RemoteDataGearManager経由でacknowledgeを返すのが正しい</li>
  <li>しかし、acknowledgeが重複してしまう</li>
  <li>メタプログラミングを利用してこの重複を消すことは可能
    <ul>
      <li>しかし煩雑</li>
    </ul>
  </li>
</ul>

</div>


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