Mercurial > hg > Papers > 2014 > toma-master
view slides/master.html @ 47:e32c9a53310c
fix
author | Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp> |
---|---|
date | Thu, 06 Feb 2014 21:12:49 +0900 |
parents | 6fbc1771d2be |
children | 7c7afe38c9d6 |
line wrap: on
line source
<!DOCTYPE html> <!-- Google HTML5 slide template Authors: Luke Mahé (code) Marcin Wichary (code and design) Dominic Mazzoni (browser compatibility) Charles Chen (ChromeVox support) URL: http://code.google.com/p/html5slides/ --> <html> <head> <title> 関数型言語 Haskell による並列データベースの実装 </title> <meta charset='utf-8'> <script src='http://web.amothic.com/html5slides/slides.js'></script> </head> <style> /* Your individual styles here, or just use inline styles if that’s what you want. */ </style> <body style='display: none'> <section class='slides layout-regular template-concurrency'> <!-- Your slides (<article>s) go here. Delete or comment out the slides below. --> <article> <h1> 関数型言語 Haskell による並列データベースの実装 </h1> <p> Daichi TOMA <br> Feb 4, 2014 </p> </article> <article> <h3> 研究概要 </h3> <p> Haskell を用いて並列データベースを実装した。 </p> <p> 読み込みに関して 12 コアで実行した場合、10.77 倍 という性能向上率が確認できた。 </p> <p> また、Web 掲示板サービスを開発し、Java と比較して読み込みで 1.87 倍、書き込みで 2.3倍の性能が確認できた。 </p> </article> <article> <h3> 研究背景 </h3> <p> Web サービスの脆弱性を悪用されると多大な被害がでる </p> <p> Haskell は型検査でバッファオーバーフローや、クロスサイトスクリプティング、SQL インジェクションを防げる </p> <p> Haskell を用いてデータベースと Web サービスの開発を行う </p> </article> <article> <h3> Haskell </h3> <p> Haskellは純粋関数型プログラミング言語 </p> <p> 純粋とは、引数が同じならば関数が必ず同じ値を返す </p> <pre> fib :: Int −> Int fib 0 = 0 fib 1 = 1 fib n = fib (n-2) + fib (n-1) </pre> </article> <article> <h3> 実行時型エラーがない </h3> <p> Haskellは、実行時に型に起因するエラーが起きない </p> <p> [1,2,3]のリストに文字'a'を追加することや、['a','b','c']のリストに 1 を追加することはできない </p> <p> コンパイル時にエラーになる </p> <pre> abc = 'a' : [1,2,3] cde = 1 : ['a','b','c'] </pre> <pre> -- リスト型の定義 data [] a = [] | a : [a] </pre> </article> <article> <h3> 型推論 </h3> <p> Haskell が型を推論してくれる </p> <pre> getChildren node path = elems (children (getNode node path)) </pre> <p> getChildren は、Node と Path を受け取って Node のリストを返すことがわかる </p> <pre> *Jungle> :type getChildren getChildren :: Node -> Path -> [Node] </pre> <p> 他の型の情報を利用して推論できる </p> <pre> getNode :: Node -> Path -> Node elems :: Map k a -> [a] children :: Node -> Map Int Node </pre> </article> <article> <h3> モナド </h3> <p> モナドを使うことで文脈を保ったまま関数を繋いでいくことができる </p> <p> Maybe モナドを用いて説明する </p> <p> Maybe 型は、失敗する可能性を扱うデータ型である </p> <pre> data Maybe a = Nothing | Just a </pre> </article> <article> <h3> モナド - 型クラス </h3> <p> Maybe 型は、モナド型クラスのインスタンスである。 </p> <p> 型クラスは、オブジェクト指向のクラスとは異なる。 </p> <p> 型の振る舞いを定義するもので、今回は Maybe 型はモナドとして振る舞えるという意味になる。 </p> <pre> instance Monad Maybe where return x = Just x Nothing >>= f = Nothing Just x >>= f = f x </pre> <p> モナドとして振る舞うためには2つの関数を定義する。 return と、>>= (bind)である。 </p> </article> <article> <h3> モナド - 関数を繋ぐ </h3> <p> 失敗するかもしれないという文脈を保ったまま関数を繋ぐ </p> <pre> up 4 = Nothing up n = Just (n + 1) down 0 = Nothing down n = Just (n - 1) </pre> <pre> return 3 >>= down >>= down >>= up >>= up </pre> <pre> instance Monad Maybe where return x = Just x Nothing >>= f = Nothing Just x >>= f = f x </pre> </article> <article> <h3> モナドを使わないで同じことをする </h3> <pre> return 3 >>= down >>= down >>= up >>= up </pre> <pre> updown :: Maybe Int updown = case down 3 of Nothing -> Nothing Just place1 -> case down place1 of Nothing -> Nothing Just place2 -> case up place2 of Nothing -> Nothing Just place3 -> up place3 </pre> </article> <article> <h3> 非破壊的木構造 </h3> <p> データベースを線形に性能向上させたければ、各コアからデータに同時にアクセスできるようにし並列度を高める </p> <p> 非破壊的木構造という手法を用いる。 非破壊的木構造は、元となる木構造を書き換えずに編集できる。 </p> <p> 既にあるデータを変更しないので、データの競合状態が発生しない。並列に読み書きできる </p> <br> <div align="center"> <img src="images/concurrent_edit.png" width="400px"> </div> </article> <article> <h3> 非破壊的木構造 - ルートノード </h3> <p> どの木構造が最新なのかを表す情報 </p> <p> 状態を持つのはここだけで、並列度を高めるにはルートノードの設計が重要 </p> <div align="center"> <img src="images/rootnode.png" width="400px"> </div> </article> <article> <h3> データベースの設計 - 並列度を高めるために </h3> <p> できるだけルートノードに触る範囲を狭くする </p> <p> ルートノードを更新する関数と、編集する関数を切り分ける </p> </article> <article> <h3> Haskell でのルートノードの管理 </h3> <p> ソフトウェア・トランザクショナル・メモリ (STM) を使う </p> <p> STM は、排他制御を行わずに共有データを扱える </p> <p> STM は、他のスレッドによる変更を考慮せずに共有データを変更する </p> <p> 変更をトランザクションとしてコミットする時に以下のことがひとつだけ起こる </p> <ul> <li>同じデータを平行して変更したスレッドが他になければ、加えた変更が他のスレッドから見えるようになる <li>そうでなければ、変更を実際に実行せずに破棄し、変更の処理を再度実行する。 </ul> </p> </article> <article> <h3> Jungle のデータ型 </h3> <p> Jungle は、非破壊的木構造を扱う並列データベース </p> <pre> -- Jungle のデータ型 data Jungle = Jungle (TVar (Map String Tree)) -- Tree のデータ型 data Tree = Tree (TVar Node) String -- Node のデータ型 data Node = Node (Map Int Node) (Map String ByteString) </pre> <p> TVarがついてるのはSTMを使ってる変数 </p> <p> Map は連想配列 </p> </article> <article> <h3> Jungle の実装 </h3> <p> Jungle は複数の Tree を持っている。 Tree には名前がついており、最新のルートノードを持っている。 </p> <div align="center"> <img src="images/jungle_type.png" width="600px"> </div> </article> <article class="smaller"> <h3> 状態を扱う関数 </h3> <pre> createJungle :: IO Jungle createTree :: Jungle -> String -> IO () getRootNode :: Jungle -> String -> IO Node updateRootNode :: Jungle -> String -> Node -> IO () updateRootNodeWith :: (Node -> Node) -> Jungle -> String -> IO () </pre> <p> IO が付いているものは何かしらの状態の変更を行う関数である。 </p> <p> この関数の型の -> で繋がっているものは複数の引数を取ることを表している。 </p> <p> 実際にはHaskellの関数はカリー化されているため、全ての関数は一度に一つの引数だけを取る。 複数の引数を取るようにみえる関数は、実際には1つの引数を取り、その次の引数を受け取る関数を返す。 </p> <p> createJungle は Jungle の作成<br> createTree は、Jungle と 木の名前を受け取って、木を作成する<br> getRootNode は、Jungle と 木の名前を受け取って、ルートノードを返す<br> updateRootNode は、Jungle と 木の名前と Node を受け取ってルートノードを更新する。<br> updateRootNodeWith は、Node を編集する関数(Node -> Node)と Jungle と 木の名前を取って、ルートノードに関数を適用して更新する。<br> </p> </article> <article class="smaller"> <h3> 木構造の編集 </h3> <pre> addNewChildAt :: Node -> Path -> Node deleteChildAt :: Node -> Path -> Position -> Node putAttribute :: Node -> Path -> String -> B.ByteString -> Node deleteAttribute :: Node -> Path -> String -> Node </pre> <p> 状態の変更は行わない。 </p> <p> addNewChildAt は、指定された Path の Node に子を追加する<br> deleteChildAt は、指定された Path の Node の Position の子を削除する<br> putAttribute は、指定された Path の Node に キーと値を追加する<br> deleteAttribute は、指定された Path の Node のキーにあてはまる値を削除する<br> </p> </article> <article class="smaller"> <h3> 木構造の参照 </h3> <pre> getAttributes :: Node -> Path -> String -> Maybe B.ByteString getChildren :: Node -> Path -> [Node] assocsChildren :: Node -> Path -> [(Int, Node)] assocs :: Node -> Path -> [(String, B.ByteString)] numOfChild :: Node -> Path -> Int currentChild :: Node -> Path -> Maybe Node </pre> <p> getAttributes は、指定された Path の Node に存在する属性を キー を用いて参照できる<br> getChildren は、指定されたPath のNode が持つ全ての子を Node のリストとして返す<br> assocsChildren は、指定された Path の Node が持つ全ての子を Position とのタプルにし、そのペアのリストを返す<br> assocsAttribute は、指定された Path の Node が持つ全ての属性を、キーと値のペアとし、そのペアのリストを返す<br> numOfChild では、指定された Path の Node が持つ子どもの数を取得できる<br> currentChild では、指定された Path の Node が持つ最新の子を取得できる<br> </p> </article> <article> <h3> 性能計測 </h3> <p> Jungle がマルチコアプロセッサで性能が出るのか、実用的なWebサービスが提供できるのか確認する </p> <p> 性能の計測に用いるサーバの仕様<br> ハイパースレッディングで24コアまで使える </p> <table> <tr> <th>名前</th> <th>概要</th> </tr> <tr> <td>CPU</td> <td>Intel(R) Xeon(R) CPU X5650@2.67GHz * 2</td> </tr> <tr> <td>コア数</td> <td>12</td> </tr> <tr> <td>メインメモリ</td> <td>126 GB</td> </tr> <tr> <td>OS</td> <td>Fedora 14</td> </tr> </table> </article> <article class="smaller"> <h3> 性能計測 - 読み込みの計測結果 </h3> <p> 木構造の読み込みにかかる時間を計測する </p> <p> 12 スレッドで実行時に 10.77 倍の性能向上 </p> <p> ハイパースレッディングは遅くなったりと安定しない </p> <table> <tr> <th>CPU数</th> <th>実行時間</th> </tr> <tr> <td>1</td> <td>59.77 s</td> </tr> <tr> <td>2</td> <td>33.36 s</td> </tr> <tr> <td>4</td> <td>15.63 s</td> </tr> <tr> <td>8</td> <td>8.10 s</td> </tr> <tr> <td>12</td> <td>5.55 s</td> </tr> <tr> <td>16</td> <td>5.65 s</td> </tr> <tr> <td>20</td> <td>5.23 s</td> </tr> <tr> <td>24</td> <td>5.77 s</td> </tr> </table> </p> </article> <article> <h3> 性能計測 - 読み込みの計測結果 </h3> <p> ハイパースレッディングは安定しないため、12 スレッドまでの性能向上率 </p> <div align="center"> <img src="images/read.png" width="700px"> </div> </article> <article class="smaller"> <h3> 性能計測 - 書き込みの計測結果 </h3> <p> 木構造の書き込みにかかる時間を計測する </p> <p> 2 スレッドで 1.55 倍の性能向上<br> 12 スレッドで実行時に 3.86 倍の性能向上 </p> <p> ハイパースレッディングは12スレッド以降遅くなっている </p> <table> <tr> <th>CPU数</th> <th>実行時間</th> </tr> <tr> <td>1</td> <td>52.68 s</td> </tr> <tr> <td>2</td> <td>33.92 s</td> </tr> <tr> <td>4</td> <td>20.11 s</td> </tr> <tr> <td>8</td> <td>15.31 s</td> </tr> <tr> <td>12</td> <td>13.62 s</td> </tr> <tr> <td>16</td> <td>14.92 s</td> </tr> <tr> <td>20</td> <td>18.62 s</td> </tr> <tr> <td>24</td> <td>16.63 s</td> </tr> </table> </p> </article> <article> <h3> 性能計測 - 書き込みの計測結果 </h3> <br> <div align="center"> <img src="images/write.png" width="700px"> </div> </article> <article> <h3> 考察 </h3> <p> 書き込みの性能向上率が低い </p> <p> 木を登録する際、他のスレッドから登録があった場合、ソフトウェア・トランザクショナル・メモリが処理をやり直すため遅いと考えられる。 </p> <p> 書き込みより読み込みが多用されるシステムに向いている。 </p> </article> <article> <h3> 性能計測 - Webサービスに組み込んでの性能評価 </h3> <p> Haskell の HTTP サーバ Warp と組み合わせて Web掲示板サービスを開発する。 </p> <p> weighttpを用いて掲示板に読み込みと書き込みで負荷をかける。リクエストの総数は100万 </p> <p> Warp は、ハイパースレッディングで明らかに遅くなるので、12コアまでの計測とする。 </p> </article> <article> <h3> 性能計測 - Webサービスに組み込んでの性能評価 読み込み </h3> <p> 読み込み </p> <p> 12 スレッド時に 2.14 倍 </p> <table> <tr> <th>CPU数</th> <th>実行時間</th> </tr> <tr> <td>1</td> <td>60.72 s</td> </tr> <tr> <td>2</td> <td>37.74 s</td> </tr> <tr> <td>4</td> <td>28.97 s</td> </tr> <tr> <td>8</td> <td>27.73 s</td> </tr> <tr> <td>12</td> <td>28.33 s</td> </tr> </table> </article> <article> <h3> 性能計測 - Webサービスに組み込んでの性能評価 書き込み </h3> <p> 書き込み </p> <p> 12 スレッド時に 1.65 倍 </p> <table> <tr> <th>CPU数</th> <th>実行時間</th> </tr> <tr> <td>1</td> <td>54.16 s</td> </tr> <tr> <td>2</td> <td>36.71 s</td> </tr> <tr> <td>4</td> <td>31.74 s</td> </tr> <tr> <td>8</td> <td>31.58 s</td> </tr> <tr> <td>12</td> <td>32.68 s</td> </tr> </table> </article> <article class="smaller"> <h3> Warp の問題 </h3> <p> Warp がボトルネックとなってしまっている。 Warp は現状あまり並列化効果がでていない。 </p> <p> アクセスした際に、"hello, world" という文字列を返すだけのプログラムを作成し計測する。 </p> <p> Jungle を組み込んだ時と比較して、読み込みの場合はほとんど差がない。 </p> <table> <tr> <th>CPU数</th> <th>実行時間</th> </tr> <tr> <td>1</td> <td>49.28 s</td> </tr> <tr> <td>2</td> <td>35.45 s</td> </tr> <tr> <td>4</td> <td>25.70 s</td> </tr> <tr> <td>8</td> <td>27.90 s</td> </tr> <tr> <td>12</td> <td>29.23 s</td> </tr> </table> </article> <article> <h3> 性能計測 - Java との比較 </h3> <table> <tr> <th>測定</th> <th>Haskell</th> <th>Java</th> </tr> <tr> <td>読み込み</td> <td>28.33 s</td> <td>53.13 s</td> </tr> <tr> <td>書き込み</td> <td>32.68 s</td> <td>76.4 s</td> </tr> </table> <p> 読み込みで 1.87 倍、書き込みで 2.3 倍の性能差が出ている </p> <p> 書き込みが読み込みより性能差が出ている理由として遅延評価が考えられる。 Haskell の遅延評価は必要でなければ計算しないため、例えば木構造への書き込みが多い時に必要のない木は計算しないなどを行うことができる。 </p> </article> <article> <h3> 性能計測 - 書き込みと読み込みを同時に行った場合 </h3> <p> 書き込みごとに毎回読み込みを挟むことで、遅延評価ではなく即時評価させる。 </p> <table> <tr> <th>CPU数</th> <th>実行時間</th> </tr> <tr> <td>1</td> <td>141.40 s</td> </tr> <tr> <td>2</td> <td>70.87 s</td> </tr> <tr> <td>4</td> <td>54.32 s</td> </tr> <tr> <td>8</td> <td>55.13 s</td> </tr> <tr> <td>12</td> <td>58.60 s</td> </tr> </table> <p> 結果が明らかに遅くなっている。 12 スレッドで実行した際、 まだ Java より速いが性能差は、1.30 倍である。 </p> <p> シングルスレッドで実行した場合と比較した時、12 スレッドで 2.40 倍の性能向上が見られる。 </p> </article> <article> <h3> まとめ </h3> <p> 純粋関数型言語 Haskell を用いて並列データベースの実装をおこなった </p> <p> 読み込みに関して 12 コアで実行した場合、10.77 倍 という性能向上率が確認できた </p> <p> また、Web 掲示板サービスを開発し、Java と比較して読み込みで 1.87 倍、書き込みで 2.3倍の性能が確認できた </p> </article> <article> <h3> 今後の課題 </h3> <p> 書き込み処理の性能向上 </p> <p> 分散データベースとしての実装 </p> <p> 永続性の実装 </p> </article> </body> </html>