Mercurial > hg > Papers > 2014 > toma-master
changeset 71:98a55a935619
describe slides without rpar
author | Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp> |
---|---|
date | Thu, 13 Feb 2014 13:02:59 +0900 |
parents | 0b1a059c49fa |
children | c119afd8e948 |
files | slides/images/list.png slides/master.html |
diffstat | 2 files changed, 195 insertions(+), 510 deletions(-) [+] |
line wrap: on
line diff
--- a/slides/master.html Thu Feb 13 12:08:24 2014 +0900 +++ b/slides/master.html Thu Feb 13 13:02:59 2014 +0900 @@ -37,23 +37,20 @@ <!-- Your slides (<article>s) go here. Delete or comment out the slides below. --> - <article> +<article> <h1> 関数型言語 Haskell による並列データベースの実装 </h1> - <p> - Daichi TOMA - <br> - Feb 12, 2014 - </p> - </article> + <p> Daichi TOMA <br> + Feb 12, 2014 </p> +</article> <article> <h3> 研究概要 </h3> <p> Haskell を用いて信頼性の高い並列データベースを実装した </p> - <p> Haskell は rseq により並列実行を指示し、STMにより共有データを実現する</p> + <p> Haskell は rpar により並列実行を指示し、STMにより共有データを実現する</p> <p> Haskellの並列処理は、最新のコンパイラで性能がでることがわかった</p> <p> 読み込みに関して 12 コアで実行した場合、10.37 倍 という性能向上率が確認できた </p> <p> Web 掲示板サービスを開発し、Java と比較して読み込みで 3.25 倍、書き込みで 3.78 倍の性能差が確認できた </p> - <p> Network および IO の並列性能はあまり出てないことがわかった</p> + <p> Network の並列性能はあまり出てないことがわかった</p> </article> <article> @@ -62,23 +59,26 @@ <p> 脆弱性の大半はBuffer Overflowや、Cross site scripting、SQL injection </p> <p> これらは型エラー。Haskell は、これらをコンパイル時にチェック</p> <p> 入力として受け取った文字列が、HTML型に実行時に自動変換されたりしない<br> - ⇒ クロスサイトスクリプティングが防げる </p> + ⇒ Cross site scripting が防げる </p> </article> <article> - <h3> Haskellの型検査の例(list) </h3> - : - / \ - 1 [] + <h3> Haskellの型検査の例(list) </h3> + <p> : はlistに要素を追加するための演算子 </p> + <p> 1 : 2 : 3 : [] == [1,2,3] </p> + <br> + <div align="center"> + <img src="images/list.png" width="200px"> + </div> <pre> -let a = 0 : [1,2,3] -- [0,1,2,3] +let a = 0 : [1,2,3] -- [0,1,2,3] let b = 'a' : [1,2,3] -- error let c = 1 : ['a','b','c'] -- error </pre> </article> <article> - <h3> 型推論 </h3> + <h3> 型推論 </h3> <p> 前提として関数に以下のような型が定義されているとする </p> <pre> getNode :: Node -> Path -> Node @@ -94,7 +94,7 @@ *Jungle> :type getChildren getChildren :: Node -> Path -> [Node] </pre> - </article> +</article> <article> <h3> 実用的なデータベースには並列実行が必須 </h3> @@ -107,34 +107,26 @@ <p> モナドの例:IOモナド、エラーモナド、STMモナド </p> </article> - Haskell の並列処理 -rparの例 <article> <h3> - Haskell の遅延評価 + Haskell の並列処理 </h3> <p> - 並列実行は遅延評価が問題になる - </p> - <p> - haskell では値が必要となるまで式の評価が行われない - </p> - <p> - 並列に実行するように指示しても、評価が行われなければ並列に動かない + rparの例 </p> </article> - <article> - <h3> - Haskell の遅延評価 - </h3> - <p> - sprintは評価を行わずに値を表示するコマンド - </p> - <p> - _ は未評価の式を表し、thunk と呼ばれる - </p> +<article> + <h3> Haskell の遅延評価 </h3> + <p> 並列実行は遅延評価が問題になる </p> + <p> haskell では値が必要となるまで式の評価が行われない </p> + <p> 並列に実行するように指示しても、評価が行われなければ並列に動かない </p> +</article> +<article> + <h3> Haskell の遅延評価 </h3> + <p> sprintは評価を行わずに値を表示するコマンド </p> + <p> _ は未評価の式を表し、thunk と呼ばれる </p> <pre> ghci> let y = map (+1) [1,2,3] :: [Int] ghci> :sprint y @@ -148,26 +140,15 @@ ghci> :sprint y y = [2,_,_] </pre> - <p> - 並列実行時には出力などを挟んで強制的に即時評価されるようにする - </p> - </article> + <p> 並列実行時には出力などを挟んで強制的に即時評価されるようにする </p> +</article> - <article> - <h3> - データベースの設計 - 非破壊的木構造 - </h3> - <p> - マルチコア上で、データベースの性能向上をさせるためには、各コアからデータに同時にアクセスできるようにする - </p> - <p> - 非破壊的木構造という手法を用いる。 - - 非破壊的木構造は、元となる木構造を書き換えずに編集できる。 - </p> - <p> - 既にあるデータを変更しないので、データの競合状態が発生しない。並列に読み書きできる - </p> +<article> + <h3> データベースの設計 - 非破壊的木構造 </h3> + <p> マルチコア上で、データベースの性能向上をさせるためには、各コアからデータに同時にアクセスできるようにする </p> + <p> 非破壊的木構造という手法を用いる。</p> + <p> 非破壊的木構造は、元となる木構造を書き換えずに編集できる。 </p> + <p> 既にあるデータを変更しないので、データの競合状態が発生しない。並列に読み書きできる </p> <br> <div align="center"> <img src="images/concurrent_edit.png" width="400px"> @@ -175,11 +156,11 @@ </article> <article> - <h3> Haskell でのRoot Node の管理 </h3> + <h3> Haskell でのRoot Node の管理 </h3> - <p> Root Node は最新のNode → 状態を持つ</p> + <p> Root Node は最新のNode → 状態を持つ</p> <p> 共有された状態を作るには Software Transactional Memory (STM) を使う </p> - <p> STM は、ノンブロッキングで共有データを扱える </p> + <p> STM は、Non-Blockingで共有データを扱える </p> <p> 共有データの変更は以下の様に行われる </p> <ul> <li>他から変更がなければそのまま適用 @@ -187,96 +168,63 @@ </ul> </article> - - <article> - <h3> - 非破壊的木構造データベース Jungle - </h3> - <p> - 非破壊的木構造を扱うデータベース Jungle を開発した - </p> - <p> - Jungle の基本的な使い方 - </p> +<article> + <h3> 非破壊的木構造データベース Jungle </h3> + <p> 非破壊的木構造を扱うデータベース Jungle を開発した </p> + <p> Jungle の基本的な使い方 </p> <ul> <li> 木構造を保持する Jungle を作成 <li> Jungle に新しい木を作成 - <li> 木から最新のルートノードを取ってきて、データを参照する - <li> もしくは、ノードを編集し、木の最新のルートノードを更新する + <li> 木からRoot Nodeを取ってきて、データを参照 + <li> もしくは、Node を編集し、木のRoot Nodeを更新 </ul> - </article> +</article> - <article> - <h3> - 木構造を保持する Jungle の作成 - </h3> -<pre> -data Jungle = Jungle (TVar (Map String Tree)) -</pre> - <p> - TVarがついているのはSTMを使っている変数 - </p> - <p> - 各スレッドから新しく木を作ったりできるように木構造の保持にSTMを使っている - </p> - <p> - Jungle は複数の Tree を名前で管理する - </p> +<article> + <h3> 木構造を保持する Jungle の作成 </h3> + <p> 各スレッドから新しく木を作ったりできるように木構造の保持にSTMを使っている </p> + <p> Jungle は複数の Tree を名前で管理する </p> <div align="center"> <img src="images/jungle_type.png" width="500px"> </div> - </article> - +</article> - <article> - <h3> - 木から最新のルートノードを取ってくる - </h3> - <p> - 木から最新のルートノードを取ってくるにはgetRootNodeという関数を使う - </p> +<article> + <h3> 木からRoot Nodeを取ってくる </h3> + <p> 木からRoot Nodeを取ってくるにはgetRootNodeという関数を使う </p> + <p> 内部でSTMを使っているのでIOモナドを返す</p> <pre> getRootNode :: Jungle -> String -> IO Node </pre> - <p> - Jungleと木の名前を渡すと最新のルートノードが返ってくる - </p> - </article> + <p> Jungleと木の名前を渡すと最新の Node が返ってくる </p> +</article> <article> - <h3> ノード </h3> + <h3> Node </h3> <p> Node はChildren(Node)とattributesを持つ </p> - <p> Jungle の参照や変更の関数は全てノードに対して行う </p> + <p> Jungle の参照や変更の関数は全て Node に対して行う </p> <div align="center"> <img src="images/node_component.png" width="500px"> </div> </article> <article> - <h3> ノードを編集し、木の最新のルートノードを更新する </h3> + <h3> Node を編集し、木の Root Node を更新する </h3> <p> updateRootNodeやupdateRootNodeWithで更新できる </p> <p> 内部でSTMを使っているのでIOモナドを返す</p> <pre> updateRootNode :: Jungle -> String -> Node -> IO () updateRootNodeWith :: (Node -> Node) -> Jungle -> String -> IO () </pre> - <p> updateRootNodeには編集したNodeを渡すことで木の最新のルートノードを更新できる </p> - <p> updateRootNodeWithにはNodeを編集する関数を渡すことで木の最新のルートノードを更新できる </p> + <p> updateRootNodeには編集したNodeを渡すことで木の Root Node を更新できる </p> + <p> updateRootNodeWithにはNodeを編集する関数を渡すことで木の Root Node を更新できる </p> </article> - <article> - <h3> - 性能計測 - </h3> - <p> - Jungle がマルチコアプロセッサで性能が出るのか、実用的なWebサービスが提供できるのか確認する - </p> - <p> - 性能の計測に用いるサーバの仕様 - </p> - ハイパースレッディングで24コアまで使える<br> - ハイパースレッディングはおよそ20%程度クロックあたりの性能が向上する - </p> +<article> + <h3> 性能計測 </h3> + <p> Jungle がマルチコアプロセッサで性能が出るのか、実用的なWebサービスが提供できるのか確認する </p> + <p> 性能の計測に用いるサーバの仕様 </p> + <p> ハイパースレッディングで24コアまで使える<br> ハイパースレッディングはおよそ20%程度クロックあたりの性能が向上する </p> <table> <tr> <th>名前</th> @@ -299,411 +247,148 @@ <td>Fedora 19</td> </tr> </table> - </article> +</article> - <article> - <h3> - Haskell コンパイラ - </h3> - <p> - Haskell のコンパイラには The Glasgow Haskell Compiler (GHC) を利用する - </p> - <p> - 現在の GHC の安定版 7.6.3 は並列時にIOマネージャーがスケールしないという問題がある - </p> - <p> - リリース候補版である 7.8 を用いることにより、よりよい性能を得ることができた - </p> - </article> - - <article> - <h3> - 親和性機能(affinity)を利用する - </h3> - <p> - 親和性機能とは OS スレッドをCPUコアに固定する機能 - </p> - <p> - 並列実行時の性能が向上するため性能計測で利用する - </p> - <p> - Haskell は実行時に -qa オプションを使うこと親和性機能を使うように指示できる - </p> - </article> +<article class="smaller"> + <h3> Haskell コンパイラ </h3> + <p> Haskell のコンパイラには The Glasgow Haskell Compiler (GHC) を利用する </p> + <p> 現在の GHC の安定版 7.6.3 は並列時にIOマネージャーがスケールしないという問題がある </p> + <p> リリース候補版である 7.8 を用いることにより、よりよい性能を得ることができた </p> + <div align="center"> + <img src="images/ghc.png" width="600px"> + </div> +</article> - <article class="smaller"> - <h3> - 読み込みの性能計測 - </h3> - <p> - 木構造の読み込みにかかる時間を計測する - </p> - <p> - 12 スレッドで親和性機能を使って実行した場合 10.37 倍の性能向上 - </p> - <p> - 親和性機能を使って24スレッドで実行すると実行時間が大幅に伸びる。 - スレッドが CPU に固定されるため、性能計測以外のプログラムがうまくスケジューリングされないためだと考えられる - </p> - <table> - <tr> - <th>CPU数</th> - <th>親和性機能なし</th> - <th>親和性機能あり</th> - </tr> - <tr> - <td>1</td> - <td>60.95 s</td> - <td>61.00 s</td> - </tr> - <tr> - <td>2</td> - <td>30.83 s</td> - <td>33.95 s</td> - </tr> - <tr> - <td>4</td> - <td>15.49 s</td> - <td>16.10 s</td> - </tr> - <tr> - <td>8</td> - <td>10.31 s</td> - <td>8.79 s</td> - </tr> - <tr> - <td>12</td> - <td>8.49 s</td> - <td>5.88 s</td> - </tr> - <tr> - <td>16</td> - <td>5.82 s</td> - <td>5.81 s</td> - </tr> - <tr> - <td>20</td> - <td>6.54 s</td> - <td>5.48 s</td> - </tr> - <tr> - <td>24</td> - <td>8.21 s</td> - <td>125.09 s</td> - </tr> - </table> - </p> - </article> - - <article class='nobackground'> - <h3> - 読み込みの性能計測 - </h3> - <p> - 親和性機能を使った場合、実コアの12スレッドまでほぼ線形にスケールする - </p> - <div align="center"> - <img src="images/read.png" width="700px"> - </div> - </article> +<article> + <h3> 親和性機能(affinity)を利用する </h3> + <p> 親和性機能とは OS スレッドをCPUコアに固定する機能 </p> + <p> 並列実行時の性能が向上するため性能計測で利用する </p> + <p> Haskell は実行時に -qa オプションを使うこと親和性機能を使うように指示できる </p> +</article> - <article class="smaller"> - <h3> - 書き込みの性能計測 - </h3> - <p> - 木構造の書き込みにかかる時間を計測する - </p> - <p> - 12 スレッドで親和性機能を使って実行した場合 3.82 倍の性能向上 - </p> - <p> - 読み込みと同様に親和性機能を使って24スレッドで実行すると実行時間が大幅に伸びる - </p> - <table> - <tr> - <th>CPU数</th> - <th>親和性機能なし</th> - <th>親和性機能あり</th> - </tr> - <tr> - <td>1</td> - <td>49.70 s</td> - <td>49.61 s</td> - </tr> - <tr> - <td>2</td> - <td>27.77 s</td> - <td>30.76 s</td> - </tr> - <tr> - <td>4</td> - <td>18.06 s</td> - <td>18.05 s</td> - </tr> - <tr> - <td>8</td> - <td>16.66 s</td> - <td>12.50 s</td> - </tr> - <tr> - <td>12</td> - <td>15.62 s</td> - <td>12.96 s</td> - </tr> - <tr> - <td>16</td> - <td>14.91 s</td> - <td>13.11 s</td> - </tr> - <tr> - <td>20</td> - <td>15.31 s</td> - <td>13.84 s</td> - </tr> - <tr> - <td>24</td> - <td>18.11 s</td> - <td>71.66 s</td> - </tr> - </table> - </p> - </article> - - <article class='nobackground'> - <h3> - 書き込みの性能計測 - </h3> - <p> - 4 倍程度で性能向上が頭打ちになっている - </p> - <p> - 同時に書き込みがあった場合、STMが処理をやり直すため並列度が下がる - </p> +<article class="smaller"> + <h3> 読み込みの性能計測 </h3> + <p> 木構造の読み込みにかかる時間を計測する </p> + <p> 12 スレッドで親和性機能を使って実行した場合 10.37 倍の性能向上<br> + →ほぼ線形にスケールする</p> + <p> 親和性機能を使った時 24スレッドで実行すると実行時間が大幅に伸びる </p> <div align="center"> - <img src="images/write.png" width="700px"> + <img src="images/read.png" width="600px"> </div> </article> - <article> +<article class="smaller"> + <h3> 書き込みの性能計測 </h3> + <p> 木構造の書き込みにかかる時間を計測する </p> + <p> 12 スレッドで親和性機能を使って実行した場合 3.82 倍の性能向上<br> + → 同時に書き込みがあった場合、STMが処理をやり直すため並列度がでない</p> + <p> 読み込みと同様に親和性機能を使って24スレッドで実行すると実行時間が大幅に伸びる </p> + <div align="center"> + <img src="images/write.png" width="600px"> + </div> +</article> + +<article> + <h3> 読み込みと書き込みの考察 </h3> + <p> 読み込みが高速 </p> + <p> 書き込みは、同時に書き込まれた場合 STMが処理のやり直しをするため、並列度がでない </p> + <p> 親和性機能を使った場合 24 スレッドでは遅くなる<br> + ⇒ 全てのCPUコアにスレッドを固定することになりスケジューリングがうまくいかないためだと考えられる + <p> 書き込みより読み込みが多用されるシステムに向いている </p> +</article> + +<article> + <h3> Webサービスに組み込んでの性能計測 </h3> + <p> Haskell の HTTP サーバ Warp と組み合わせて Web掲示板サービスを開発する </p> + <p> 測定ツール weighttp を用いて掲示板に読み込みと書き込みで負荷をかける。 </p> + <p> Warp は、ハイパースレッディングで明らかに遅くなるので使わない </p> +</article> + +<article> + <h3> ネットワークのボトルネック </h3> + <p> ネットワークのボトルネックが大きい </p> + <p> 並列環境でどのようにスケールするか計測したいため、ネットワークを介さずに性能計測を行う </p> + <br> + <div align="center"> + <img src="images/request.png" width="300px"> + </div> +</article> + + +<article class="smaller"> + <h3> Webサービスに組み込んでの性能計測 </h3> + <p> 読み込みと書き込みの実験結果 </p> + <p> 1秒間あたりどれだけリクエストを捌けるかという指標で比較<br> + ⇒大きければ大きいほど性能が良い </p> + <p> 8 スレッドで実行時、読み込みは 6.18 倍、書き込みは3.93倍の性能向上 </p> + <p> Jungle 単体での実験結果と同じで、読み込みのほうがスケールする </p> + <div align="center"> + <img src="images/bbs.png" width="600px"> + </div> +</article> + +<article> + <h3> Java との比較 </h3> + <p> HaskellとJavaで同様のWeb掲示板サービスを用意する </p> + <p> 実環境を想定して、ブレードサーバ2台用意し、ネットワークを介して負荷をかける </p> + <p> 100 万リクエストを処理するのにかかる時間を計測 </p> + + <table> + <tr> + <th>測定 + <th>Haskell + <th>Java + </tr> + <tr> + <td>読み込み + <td>16.31 s + <td>53.13 s + </tr> + <tr> + <td>書き込み + <td>20.17 s + <td>76.4 s + </tr> + </table> + + <p> 読み込みで 3.25 倍、書き込みで 3.78 倍の性能差<br> + ⇒ Haskell は実用的なWebサービスを開発できる </p> +</article> + +<article> + <h3> Java との生産性の比較</h3> + <p> Haskell 版 Jungle は 284 行、Java 版 Jungle は 3,390 行 </p> + <p> 実装が 1/12 程度のサイズとなっている </p> + <p> 再帰的なデータ型の定義ができることや、関数の再利用が行いやすいことが要因 </p> + <p> 同じ機能を実装する場合でも、Haskell は Java と比較してコード行数が短くなる </p> +</article> + +<article> + <h3> まとめ </h3> + <p> 純粋関数型言語 Haskell を用いて並列データベースの実装をおこなった </p> + <p> 読み込みに関して 12 コアで実行した場合、10.37 倍 という性能向上率が確認できた </p> + <p> また、Web 掲示板サービスを開発し、Java と比較して読み込みで 3.25 倍、書き込みで 3.78 倍の性能が確認できた </p> +</article> + +<article> + <h3> 今後の課題 </h3> + <p> 書き込み処理の性能向上 </p> + <p> 分散データベースとしての実装 </p> + <p> 永続性の実装 </p> +</article> + +<article> <h3> - 考察 - </h3> - <p> - 読み込みが高速 - </p> - <p> - 書き込みより読み込みが多用されるシステムに向いている - </p> - </article> - - <article> - <h3> - Webサービスに組み込んでの性能計測 + ネットワークボトルネックの計測 </h3> <p> - Haskell の HTTP サーバ Warp と組み合わせて Web掲示板サービスを開発する - </p> - <p> - 測定ツール weighttp を用いて掲示板に読み込みと書き込みで負荷をかける。 - </p> - <p> - Warp は、ハイパースレッディングで明らかに遅くなるので使わない + ネットワークを介す場合と介さない場合のWarpのベンチマーク </p> - </article> - - <article class="nobackground"> - <h3> - ネットワークのボトルネック - </h3> - <p> - ネットワークのボトルネックが大きい - </p> - <p> - 並列環境でどのようにスケールするか計測したいため、ネットワークを介さずに性能計測を行う - </p> - <br> <div align="center"> <img src="images/warp.png" width="600px"> </div> - </article> - - <article> - <h3> - 実験環境 - </h3> - <p> - 3 コアを測定ツール weighttp に使い、 - 8 コアを Web 掲示板サービスに使う - </p> - <br> - <div align="center"> - <img src="images/request.png" width="400px"> - </div> - </article> - - <article> - <h3> - Webサービスに組み込んでの性能計測 - </h3> - <p> - 読み込みと書き込みの実験結果 - </p> - <p> - 1秒間あたりどれだけリクエストを捌けるかという指標で比較 - 大きければ大きいほど性能が良い - </p> - <p> - 8 スレッドで実行時、読み込みは 6.18 倍、書き込みは3.93倍の性能向上 - </p> - <table> - <tr> - <th>CPU数</th> - <th>読み込み</th> - <th>書き込み</th> - </tr> - <tr> - <td>1</td> - <td>22,624 req/s</td> - <td>28,552 req/s</td> - </tr> - <tr> - <td>2</td> - <td>43,083 req/s</td> - <td>53,765 req/s</td> - </tr> - <tr> - <td>4</td> - <td>92,548 req/s</td> - <td>98,691 req/s</td> - </tr> - <tr> - <td>6</td> - <td>119,310 req/s</td> - <td>99,009 req/s</td> - </tr> - <tr> - <td>8</td> - <td>139,965 req/s</td> - <td>112,212 req/s</td> - </tr> - </table> - </article> - - <article class='nobackground'> - <h3> - Webサービスに組み込んでの性能計測 - </h3> - <p> - Jungle 単体での実験結果と同じで、読み込みのほうがスケールする - </p> - <div align="center"> - <img src="images/bbs.png" width="700px"> - </div> - </article> +</article> - <article> - <h3> - Java との比較 - </h3> - <p> - HaskellとJavaで同様のWeb掲示板サービスを用意する - </p> - <p> - 実環境を想定して、ブレードサーバ2台用意し、ネットワークを介して負荷をかける - </p> - <p> - 100 万リクエストを処理するのにかかる時間を計測 - </p> - - <table> - <tr> - <th>測定</th> - <th>Haskell</th> - <th>Java</th> - </tr> - <tr> - <td>読み込み</td> - <td>16.31 s</td> - <td>53.13 s</td> - </tr> - <tr> - <td>書き込み</td> - <td>20.17 s</td> - <td>76.4 s</td> - </tr> - </table> - <p> - 読み込みで 3.25 倍、書き込みで 3.78 倍の性能差 - </p> - <p> - Haskell は実用的なWebサービスを開発できる - </p> - </article> - - <article> - <h3> - Java との比較 - 生産性 - </h3> - <p> - 生産性の面からも Java との比較を行う - </p> - <p> - Haskell 版 Jungle は 284 行、 - Java 版 Jungle は 3,390 行 - </p> - <p> - 実装が 1/12 程度のサイズとなっている - </p> - <p> - 再帰的なデータ型の定義ができることや、関数の再利用が行いやすいことが要因 - </p> - <p> - 同じ機能を実装する場合でも、Haskell は Java と比較してコード行数が短くなる - </p> - </article> - - <article> - <h3> - まとめ - </h3> - <p> - 純粋関数型言語 Haskell を用いて並列データベースの実装をおこなった - </p> - <p> - 読み込みに関して 12 コアで実行した場合、10.37 倍 という性能向上率が確認できた - </p> - <p> - また、Web 掲示板サービスを開発し、Java と比較して読み込みで 3.25 倍、書き込みで 3.78 倍の性能が確認できた - </p> - </article> - - <article> - <h3> - 今後の課題 - </h3> - <p> - 書き込み処理の性能向上 - </p> - <p> - 分散データベースとしての実装 - </p> - <p> - 永続性の実装 - </p> - </article> - - <article> - <h3> - GHC 7.6.3 の IO マネージャーの問題 - </h3> - <p> - GHC 7.6.3 には 並列実行時に IO マネージャーがスケールしないという問題がある - </p> - <div align="center"> - <img src="images/ghc.png" width="700px"> - </div> - </article> </body> </html>