Mercurial > hg > Papers > 2014 > toma-master
changeset 62:d11f4c6c7657
fix
author | Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp> |
---|---|
date | Wed, 12 Feb 2014 16:13:49 +0900 |
parents | b38cc4f3e242 |
children | d5ebba127662 |
files | paper/chapter1.tex paper/chapter2.tex paper/chapter3.tex paper/chapter4.tex paper/master_paper.pdf paper/thanx.tex |
diffstat | 6 files changed, 65 insertions(+), 65 deletions(-) [+] |
line wrap: on
line diff
--- a/paper/chapter1.tex Tue Feb 11 23:01:11 2014 +0900 +++ b/paper/chapter1.tex Wed Feb 12 16:13:49 2014 +0900 @@ -31,18 +31,17 @@ \subsubsection{変数の代入} 純粋関数型プログラミングでは, 変数の代入は一度のみで後から書き換えることはできない. フィボナッチ数列の関数でも, 一度引数を束縛した n を書き換えることはできない. -関数にできることは, 何かを計算してその結果を返すことだけであり, 引数が同じならば関数は必ず同じ値を返すことが保証されている. +変数への代入が一度のみのため, 関数にできることは何かを計算してその結果を返すことだけであり, 引数が同じならば関数は必ず同じ値を返すことが保証されている. この性質は関数の理解を容易にし, プログラムの証明を可能にする. 正しいと分かる単純な関数を組み合わせて, より複雑な正しい関数を組み立てていくのが関数型言語のプログラミングスタイルである. -\subsubsection{高階関数} -関数型プログラミング言語は, 関数を変数の値にすることができる. -これは, 関数を第一級オブジェクトとして扱うことができるということである. +\subsubsection{高階関数とカリー化} +関数型プログラミング言語は, 関数を変数の値として扱うことができる. Haskell は, 引数として関数を取ったり返り値として関数を返すことができる高階関数を定義できる. 高階関数の例として Haskell のカリー化が挙げられる. -Haskell では, 全ての関数は一度に一つの引数だけを取る. -2 つのInt を受け取り, 大きい方を返す max の型はソースコード\ref{src:max}のように定義できる. +Haskell では, 複数の引数を取るようにみえる関数でも, 実際には全て一度に一つだけの引数を取る. +2 つのInt を受け取り, 大きい方を返す max はソースコード\ref{src:max}のように定義できる. \begin{lstlisting}[label=src:max, caption=max関数] max :: Int -> Int -> Int @@ -51,8 +50,8 @@ else x \end{lstlisting} -この関数定義に現れる-$>$は左結合である. -複数の引数を取るようにみえる関数は, 実際には1つの引数を取り, その次の引数を受け取る関数を返す(ソースコード\ref{src:max_curry}). +このmaxの型宣言に現れる-$>$は左結合であり, +複数の引数を取るようにみえる関数は, 実際には 1 つの引数を取り, その次の引数を受け取る関数を返す(ソースコード\ref{src:max_curry}). \begin{lstlisting}[label=src:max_curry, caption=max関数のカリー化] max :: Int -> (Int -> Int) @@ -78,15 +77,14 @@ その文字列が他の文字列と同じように振る舞うとみなすことができる. \subsubsection{型の定義と型検査} -Haskell は静的型検査によりエラーを検出することができる. -Haskell では, 評価の際に型に起因するエラーが起きないことを保証している. +Haskell は静的型検査によりエラーを検出することができ, 評価の際に型に起因するエラーが起きないことを保証している. 例えば, 引数として整数を受け取る関数に文字列を渡そうとしても Haskell のコンパイラはこれを受け付けない. Haskell は, すべての式, すべての関数に型があるためコンパイル時に多くのエラーを捕まえることができる. エラーの検出の例として Haskell で最もよく使われるデータ構造であるリスト型で確認を行う. また, リスト型の定義とあわせてデータ型の定義についても説明する. -リストとは, 角括弧で包まれた同じ型の要素の並びである. [1,2,3] などと表現する. +リストとは, 角括弧で包まれた同じ型の要素の並びである. [1,2,3] や ['a','b','c'] などと表現する. リストはソースコード\ref{src:list}のように定義されている. \begin{lstlisting}[label=src:list, caption=Haskellのリスト定義] @@ -94,25 +92,25 @@ \end{lstlisting} data というのは新しい型を定義する際に利用するキーワードである. -data キーワードのすぐ後にある [] a というのが型名である. +等号の前に型の名前, 等号の後に型が取り得る値の種類を指定する. -a というのは, 型変数と呼ばれるものである. -型変数は任意の型を取ることができる. +等号の前にある [] a というのが型名である. +a というのは, 型変数と呼ばれるもので, 任意の型を取ることができる. リスト型は, Intのリスト, Floatのリストといった様々な型のリストを作ることができ, 型変数を用いてそれを実現する. 型変数が何の型になるのかという情報は実行時には決まっており, 関数に渡される際に型の不整合が起きることはない. -= の右側には, 新しい型の定義として型の値となるものを列挙する. +等号の後は, 型の値となるものを列挙する. $|$ は, もしくはという意味である. -つまりリストは, [] もしくは a : [a] という値になることが分かる. +つまりリストの型の値は, [] もしくは a : [a] になることが分かる. -[] は空リストを表す. 型名と同じであるが, 型とは名前領域が異なるため問題ない. +型の値 [] は空のリストを表す. 型名と同じであるが, 型とは名前領域が異なるため問題ない. 型名はプログラム中では注釈としてしか使われないためである. -a : [a] は再帰的なデータ構造である. +型の値 a : [a] は, リスト自身が含まれるため再帰的なデータ構造である. 値 a を : で繋げて, 再度リストの定義を呼んでいる. -この定義 [] $|$ a : [a] は, a : a : a : .. : a : [] となり, a が任意個続いた後に, [] が来ることとなる. -つまり, リストは無限に繋げることができ, リストの終端は空のリスト, つまり [] で終わる. +型の値の定義 [] $|$ a : [a] は, 展開すると a : a : a : .. : a : [] となる. a が任意個続いた後に, [] が来ることとなる. +つまり, リストは無限に繋げることができ, リストの終端は空のリスト [] で終わる. Haskell では, [1,2,3] という様にリストを表すが, これは単なるシンタックスシュガーであり, 内部では 1 : 2 : 3 : [] のように表現される. @@ -125,10 +123,9 @@ そのため文字である 'b' をIntのリスト [3,4] に付け加えることはできない. \begin{lstlisting}[label=src:error, caption=Haskellのコンパイル時エラー] -<interactive>:3:7: - Couldn't match type `Int' with `Char' - Expected type: [Char] - Actual type: [Int] +Couldn't match type `Int' with `Char' +Expected type: [Char] + Actual type: [Int] \end{lstlisting} 型検査でも捕まえられないエラーは存在する. @@ -139,7 +136,7 @@ 型推論とは, 型の宣言をせずともそれを導くのに使われた関数の型シグネチャなどから自動的に型を決定する機構のことである. 型推論のない静的型付け言語は, プログラマが型の宣言を行うことが強制されるが Haskell では型の宣言は必須ではない. -例として, 開発したデータベースで実装した getChildren という関数に対して型推論を行ってみる(ソースコード\ref{src:getchildren}). +どのように型推論が行われるのかの例として, 開発したデータベースで実装した getChildren という関数で調べる(ソースコード\ref{src:getchildren}). \begin{lstlisting}[label=src:getchildren, caption=getChildren関数] getChildren node path = elems (children (getNode node path)) @@ -177,12 +174,13 @@ プログラミングを行うにあたり, I/O 処理は欠かせないため, モナドの説明を行う. モナドとは, 型クラスの 1 つである. -型クラスは型の振る舞いを定義するものである. +型クラスとは型の振る舞いを定義するものである. ある型クラスのインスタンスである型は, その型クラスに属する関数の集まりを実装する. これは, それらの関数がその型ではどのような意味になるのか定義するということである. -モナドとなる型は, 型変数として具体型をただ 1 つ取る. +モナドとなれる型は, 型変数を 1 つ取ることができる型である. これにより何かしらのコンテナに包まれた値を実現する. +モナドとなった型はコンテナに包まれた値に対する共通の操作を行うことができる. モナドの振る舞いは型クラスとして実装し, 関数として return および $>>$= (bind) を定義する(ソースコード\ref{monad}). \begin{lstlisting}[label=monad, caption=モナドに属する関数の型] @@ -217,7 +215,6 @@ \subsubsection{Maybe モナド} 文脈を保ったまま関数を繋いでいくとはどういうことなのか, 具体例を用いて説明する. - Maybe 型は失敗する可能性を扱うデータ型である(ソースコード\ref{src:maybe}). \begin{lstlisting}[label=src:maybe,caption=Maybe型の定義] @@ -227,9 +224,9 @@ 失敗したことを表す Nothing, もしくは成功したことを表す Just a のいずれかの値を取る. Maybe 型が使われている例として, Data.Map の lookup 関数がある. -Data.Map は Key と Value を保持する辞書型のデータ構造である. -何らかの Key を渡して, Data.Map から値を取得しようとした時, 返される値は Maybe Value 型である. -何かしらの値が取得できた場合は, Just a として Value に Just がついて返される. +Data.Map はキーと値を保持する辞書型のデータ構造である. +何らかのキーを渡して, Data.Map から値を取得しようとした時, 返される値は Maybe a 型である. +何かしらの値が取得できた場合は, Just a として値に Just がついて返される. 取得できなければ, Nothing が返る. Maybe モナドを使いたい場面は, 失敗するかもしれないという計算を繋いでいく時である. @@ -282,7 +279,7 @@ \end{lstlisting} case 式は, caseとofの間の式を評価し, その値によって評価を分岐させるためのものである. -case を受け取る $->$ の左の部分には式の値を書き, その式の値によって評価を分岐させる. +case を受け取る $->$ の前の部分には式の値を書き, その式の値によって評価を分岐させる. 例えば, 3 行目は down 3 の結果が Nothing なら, Nothing を返すという意味である. Justの場合, 値をplace1という変数に束縛しその後の処理を続ける. @@ -349,10 +346,10 @@ リストを使ってどのように評価されるのか確認する. ソースコード\ref{src:list_sprint} では, まずmap関数を利用してリストの要素を全て (+1) している. しかし, この計算は必要となるまで計算されない. -直後にsprintを行うと, ただ\_が表示される. +直後にsprintを行うと, ただ \_ が表示される. リストの長さを表示する関数であるlengthを実行後に sprint を行った場合は, -リストの要素数を確認しているため、要素数分のthunkを持つリストとなる. +リストの要素数を確認しているため, 要素数分のthunkを持つリストとなる. 実際に値が必要となる関数を適用する. head はリストの先頭要素を取得する関数である. @@ -389,14 +386,13 @@ Haskellはデフォルトではシングルスレッドで走る. 並列に実行したい場合は, -threaded 付きでコンパイルし, RTS の -N オプションを付けて実行する. -N オプションで指定された数だけ, OSのスレッドが立ち上がり実行される(ソースコード\ref{concurrent}). +当然これだけでは並列に動かず, 並列に実行できるようにプログラムを書く必要がある. \begin{lstlisting}[language=bash, label=concurrent, caption=並列実行の様子] $ ghc -O2 par.hs -threaded $ ./par +RTS -N2 \end{lstlisting} -当然これだけでは並列に動かず, 並列に実行できるようにプログラムを書く必要がある. - Control.Parallel.Strategies モジュールにある, Eval モナドを用いた並列化について説明する. Eval モナドは並列処理を行うためのモナドである. Eval モナドで並列処理を行う使用例を示す(ソースコード\ref{src:evalmonad}).
--- a/paper/chapter2.tex Tue Feb 11 23:01:11 2014 +0900 +++ b/paper/chapter2.tex Wed Feb 12 16:13:49 2014 +0900 @@ -65,13 +65,13 @@ \label{fig:rootnode} \end{figure} -ルートノードはスレッド間で共有する状態を持つため, Haskell では IO モナドを用いて状態を扱う必要がある. +ルートノードはスレッド間で共有する状態を持つため, Haskell では IO モナドを用いて状態を扱う. これには, Haskell のソフトウェア・トランザクショナル・メモリ(STM)を利用する. STM はブロックせず, スレッドセーフに状態を扱うことができる. STM を利用することでロック忘れによる競合状態や, デッドロックといった問題から解放される. STM は, STM モナドという特殊なモナドの中でのみ変更できる. -STM モナドの中で変更したアクションのブロックを atomically コンビネータを使ってトランザクションとして実行する. (atomically コンビネータを用いることで IO モナドとして返される). +STM モナドの中で変更したアクションのブロックを atomically コンビネータを使ってトランザクションとして実行する(atomically コンビネータを用いることで IO モナドとして返されるため, I/O操作が可能となる). いったんブロック内に入るとそこから出るまでは, そのブロック内の変更は他のスレッドから見ることはできない. こちら側のスレッドからも他のスレッドによる変更はみることはできず, 実行は完全に孤立して行われる. トランザクションから出る時に, 以下のことが1つだけ起こる.
--- a/paper/chapter3.tex Tue Feb 11 23:01:11 2014 +0900 +++ b/paper/chapter3.tex Wed Feb 12 16:13:49 2014 +0900 @@ -61,12 +61,11 @@ \end{lstlisting} Jungle のデータ構造は, Jungle (TVar (Map String Tree)) である. + getJungleMap :: というのは, Haskell のレコード構文である. - レコード構文は, データ構造へのアクセサを提供する. getJungleMap は関数で, ソースコード\ref{src:getjunglemap}の型を持つ. これは, Jungleを受け取って, TVar (Map String Tree)を返す関数である. - レコード構文はデータ型を受け取って, :: の右側の型の値を取り出せる関数を作成すると思えば良い. \begin{lstlisting}[label=src:getjunglemap, caption=getJungleMap] @@ -74,7 +73,6 @@ \end{lstlisting} Jungle の木の取り扱いには, Haskell の Data.Map を利用している. -型名は, Map である. Map は, 連想配列を扱うことのできるデータ構造である. 平衡木を用いて, 挿入や参照が O (log n)で済むように設計されている. Data.Mapを理解するためにはリストで考えると分かりやすい(ソースコード\ref{src:map_list}). @@ -86,7 +84,7 @@ lookup' k (Map []) = Nothing lookup' k (Map ((k',a):xs)) = if k == k' then Just a - else lookup k xs + else lookup' k xs insert :: k -> a -> Map k a -> Map k a @@ -98,15 +96,18 @@ Map は, キーと値のペアのリストだと考えることができる. キーが一致する値を探す場合, lookup'を用いる. Maybe モナドを用いて, データがなければ Nothing, データがあれば Just に包んで返す. + +lookup' の定義を見ていく. $=>$ の前にある, Eq kは, 型クラスの制約である. 内部で k と k' の同値性をテストしているため, k は同値性をチェックできる型クラス Eq に属している型である必要がある. +lookup'では, 見つからなかった場合にまだ見ていないリストの残りを再度lookup'に渡して探す. +Haskell ではこのような再帰的な関数定義をよく使う. 新たにキーと値のペアを, Mapに追加するには insertを用いる. Haskell では, 受け取った引数を変更することができないため, ペアを追加した新しい Map を返す. -木の取り扱いには Haskell のソフトウェア・トランザクショナル・メモリ (STM) を利用して状態を持たせ, スレッド間で共有できるようにしてある. +木と木の名前の Map は Haskell のソフトウェア・トランザクショナル・メモリ (STM) を利用して状態を持たせ, スレッド間で共有できるようにしてある. これは, 各スレッドから木構造を新たに作成できるようにするためである. -STM は, スレッド間でデータを共有するためのツールである. STM を利用することでロック忘れによる競合状態や, デッドロックといった問題から解放される. Jungle のデータ構造の Map の前に付いている TVar というのは, Transactional variablesの略で, STM で管理する変数に対して利用する. \subsubsection{Jungle と木の作成} @@ -121,8 +122,8 @@ \label{fig:jungle} \end{figure} -木構造の識別, つまり Map の キー にはString を利用する. -String は Haskell の文字列の型で, Char のリスト [Char] の別名である. +木の名前, つまり Map の キー にはString を利用する. +String は Haskell の文字列の型で, 1文字を表す Char のリスト [Char] の別名である. Jungle を作成するには, createJungle を用いる(ソースコード\ref{src:createJungle}). empty は空のMapを作成する関数である. @@ -134,12 +135,12 @@ return (Jungle map) \end{lstlisting} - createJungleは, 新たにSTMの変数を作成する newTVar を実行する. newTVar などの STM の操作は STM モナド内で行う. 最後にatomicallyを行うことで, do 構文内がトランザクションとして実行される. STMの関数が持つ型をソースコード\ref{src:stm}に示す. +\newpage \begin{lstlisting}[label=src:stm, caption=STMの関数] newTVar :: a -> STM (TVar a) readTVar :: TVar a -> STM a @@ -168,7 +169,7 @@ 実際にユーザがJungleを利用する際は, Jungle と木の名前を使ってルートノードを取ってくるため, Tree という構造は見えない. ルートノードの情報はスレッド間で状態を共有する必要がある. -スレッドセーフに取り扱う必要があるため, この情報も Haskell の ソフトウェア・トランザクショナル・メモリ (STM) を用いて管理している. +スレッドセーフに取り扱う必要があるため, Haskell の ソフトウェア・トランザクショナル・メモリ (STM) を用いて管理している. \begin{lstlisting}[label=src:tree,caption=Treeのデータ型の定義] data Tree = Tree @@ -195,8 +196,8 @@ emptyNode = Node (empty) (empty) \end{lstlisting} -createJungleも STM を操作するため IOを返す. -Jungle の持つ, 複数の木構造と名前を関連付けた Map をreadTVarで取得する. +createTreeも STM を操作するため IOを返す. +createTree 関数では, まず始めにJungle の持つ複数の木構造と名前を関連付けた Map をreadTVarで取得する. ルートノードの管理のための STM の変数をもった Tree を作成し, Jungle の Map に insert する. そして最後に writeTVar を用いて STM を更新する. writeTVar は更新する先の変数と, 更新内容の2つを受け取る STM の関数である. @@ -209,7 +210,6 @@ createTree jungle "name of new tree here" \end{lstlisting} - \subsubsection{ルートノード} 非破壊的木構造データベース Jungle では, 木の最新の状態を更新・参照するのにルートノードを使う. ルートノードは, 最新の木構造の根がどれかの情報を保持している(図\ref{fig:getrootnode}). @@ -229,12 +229,6 @@ getRootNode 関数の定義を示す(ソースコード\ref{src:getrootnode}). -まず, readTVarでJungleが持つmapを参照する. -Haskell の where キーワードは, 計算の中間結果に名前をつけるために用いられる. -今回は, root\_node という map を受け取る関数を定義している. -root\_node map では, Jungle が持つ Map をみて取得しようとしている名前の木構造があるかどうか調べている. -木構造があった場合, rootNodeというTreeに定義されているレコード構文のアクセサ関数を使って, (TVar Node)を取得する. -最後に, (TVar Node)に対して, readTVarを行うことで最新のルートノードが取得できる. \begin{lstlisting}[label=src:getrootnode, caption=最新のルートノードの取得] getRootNode :: Jungle -> String -> IO Node @@ -246,14 +240,22 @@ Just x -> rootNode x \end{lstlisting} +まず, readTVarでJungleが持つmapを参照する. +Haskell では where キーワードを用いて計算の中間結果に名前をつけたり, 関数内で使える関数を定義できる. +今回は, root\_node という map を受け取る関数をを内部で定義している. +root\_node map では, Jungle が持つ Map をみて取得しようとしている名前の木構造があるかどうか調べている. +木構造があった場合, rootNodeというTreeに定義されているレコード構文のアクセサ関数を使って, (TVar Node)を取得する. +最後に, (TVar Node)に対して, readTVarを行うことで最新のルートノードが取得できる. + 木構造を編集する関数は全て Node を受け取って Node を返す. -その返ってきた Node をルートノードとして登録することで, 木構造の最新のルートノードが更新される. -updateRootNode は, データベースと木の名前, 変更して返ってきた木構造の 3 つを渡す. +その返ってきた Node を新しいルートノードとして登録することで, 木構造の最新のルートノードが更新される. +updateRootNode は, データベースと木の名前, 新しいルートノードの 3 つを渡す. updateRootNodeをした後は, getRootNodeで取得できるルートノードが更新された状態になっている. updateRootNode 関数の定義を示す(ソースコード\ref{src:updaterootnode}). getRootNodeと同じように, Treeの(TVar Node)を取得し, 最後にwriteTVarを用いて更新している. +\newpage \begin{lstlisting}[label=src:updaterootnode, caption=ルートノードの更新] updateRootNode :: Jungle -> String -> Node -> IO () updateRootNode (Jungle tmap) tree_name node = @@ -271,7 +273,7 @@ updateRootNodeWith 関数の定義を示す(ソースコード\ref{src:updaterootnodewith}). updateRootNodeWithでは, 一連の操作を分断せずに行うためにreadTVarからwriteTVarまで同じ STM モナド内で行っている. -atomicallyに関数にdo構文で繋げたSTMモナドを渡すことで, このブロックがトランザクションとして実行される. +atomicallyに関数にdo構文で 1 つに繋げたSTMモナドを渡すことで, このブロックがトランザクションとして実行される. \begin{lstlisting}[label=src:updaterootnodewith, caption=ルートノードの更新] updateRootNodeWith :: (Node -> Node) -> Jungle -> String -> IO () @@ -390,8 +392,7 @@ 非破壊的木構造の編集は再帰で定義できる. 左結合となる\$を使い, 対象のノードに到達するまで, addChildを繰り返す. -addChildは, 指定したノードのPositionに子を追加する. 引数として子となるノードが必要である. -addChildを繰り返すことで, 下の階層から徐々に上に作られていく. +addChildは, 指定したノードのPositionに子を追加する. 引数として子となるノードが必要であり, 下の階層から上に作られていく. addNewChildAt, deleteChildAt, putAttribute, deleteAttributeといった, 非破壊的木構造の編集は, 対象のノードに対する操作以外は全て同じである.
--- a/paper/chapter4.tex Tue Feb 11 23:01:11 2014 +0900 +++ b/paper/chapter4.tex Wed Feb 12 16:13:49 2014 +0900 @@ -74,7 +74,7 @@ OSの親和性機能を使った場合, 2 スレッドで 1.80 倍, 12 スレッドで 10.37 倍の性能向上が見られ, 12スレッド時の性能向上率が大幅に伸びている. 並列に読み込む場合, スレッドを同じプロセッサ上で実行させると性能が向上することがわかる. -しかし, 24 スレッドで実行する場合, 実行時間が大幅に伸びている. +しかし, 親和性機能を使って 24 スレッドで実行する場合, 実行時間が大幅に伸びている. スレッドがCPUに固定されるため, 性能計測以外のプログラムがうまくスケジューリングされないためだと考えられる. \begin{table}[!htbp] @@ -200,7 +200,9 @@ Warp は並列 HTTP サーバであるが, 計測環境ではネットワークがボトルネックとなってしまう. ネットワークのボトルネックがどれぐらいあるのか調査するために, アクセスした際に "hello, world" という文字列を返すだけのプログラムを作成し測定する. ネットワークを介さずに性能測定する場合, 性能測定ツール weighttp に 3 スレッド利用するため, Warp で利用するのは 8 スレッドまでとする. + weighttpの設定は, リクエストの総数 100 万, 同時に接続するコネクションの数 1,000, 実行時のスレッド数 3, HTTP Keep-Alivesを有効とする. + また, 現在の安定版である 7.6.3 は IO マネージャーに問題があるが, どの程度影響があるか調べるためにGHC 7.6.3 でコンパイルし, ネットワークを介さない状態での測定も行う. 結果を表\ref{tab:warp}に示す. @@ -314,6 +316,7 @@ Haskell は 実用的な Web サービスが開発できる. \subsubsection{Haskell の生産性} +生産性の面からも Java との比較を行う. Haskell 版 Jungle が公開している関数の数は 18 で, コード行数は284行である. Java を用いた Jungle の実装は, 3390行で, Haskell の実装は 1/12 程度のサイズとなっている.
--- a/paper/thanx.tex Tue Feb 11 23:01:11 2014 +0900 +++ b/paper/thanx.tex Wed Feb 12 16:13:49 2014 +0900 @@ -3,7 +3,7 @@ 本研究を行うにあたり, 日頃より多くの助言, ご指導いただきました河野真治助教授に心より感謝申し上げます. -本研究は, JST/CREST 研究領域「実用化を目指した組み込みシステム用ディペンダブル・ オペレーティングシステム」D-ADD 研究チームとして実施しました. +本研究は, JST/CREST 研究領域「実用化を目指した組み込みシステム用ディペンダブル・オペレーティングシステム」D-ADD 研究チームとして実施しました. 研究の機会を与えてくださった, 株式会社 Symphony の永山辰巳さんに感謝します. -また, データベースの実装にあたり, 多くの議論にお付き合い頂いた大城信康さん, 並列信頼研究室の全てのメンバーに感謝いたします. +また, データベースの実装にあたり, 多くの議論にお付き合い頂いた大城信康さん, 測定環境の構築に協力してくださった平良太貴さん, 並列信頼研究室の全てのメンバーに感謝いたします.