ページの先頭です


ページ内移動用のリンクです

  1. ホーム
  2. IIJの技術
  3. IIJ Technical Seminar
  4. 技術情報(2011年~2015年)
  5. マルチコアでスケールするようになったHaskell

マルチコアでスケールするようになったHaskell

2015年2月12日

Glasgow Haskell Compiler(GHC)は、関数型言語Haskellの主要コンパイラです。GHCは(並列性に加えて)並行性を主要な目的として長年開発されてきました。そのため、GHCには、

  • 軽量スレッド(グリーンスレッド)
  • マルチコア用の軽量スレッド・スケジューラ
  • マルチコア上での効率的なメモリアロケータ
  • マルチコア用のガベージコレクタ

など、マルチコアで簡潔に並行性を実現するための部品が揃っています。そこで、Haskellで Web サーバなどの並行プログラムを書き、GHC でコンパイルすれば、マルチコア環境でスケールするのを期待したくなります。

残念ながら GHC 7.6.3 までは、入出力を司るIOマネージャの実装にボトルネックがあり、マルチコア環境でスケールしませんでした。エール大学のAndreas Voellmy氏と筆者は、IOマネージャの改良に取り組みPDF、その成果が GHC 7.8.1 に取り込まれました。

7.8.1 以降の GHC で、これまでの並行プログラムをコンパイルすると、マルチコア環境でスケールするようになります。並行プログラムへの変更は不要です。

なお、GHC の最新のバージョンは 7.8.4 で、2月末には 7.10.1 がリリースされる予定です。一般ユーザが使う最新の Haskell Platform 2014.2.0.0 には、GHC 7.8.3 が入っています。

GHCの使い方

GHC で並行プログラムをコンパイルするときは、必ず -threaded オプションを付けてください。以下、"Server.hs" という Haskell のコードをコンパイルする例です。

% ghc -o server -threaded Server.hs

これで、"server" という実行バイナリができます。この実行バイナリには、GHC のランタイムがリンクされています。GHC のランタイムは、ユーザが指定したコマンドライン引数のうち、"+RTS" と "-RTS" で囲まれた部分を横取りします。ランタイムオプション "-N" で数を指定すると、その実行バイナリは、その分のコアを使用して実行されます。

以下は、コアを4つ使って "server" を実行させる例です。

% server +RTS -N4 -RTS

ハイパースレッドを利用している場合は、実コアではなく、ハイパースレッド化されたコアが対象となります。

数を省略すると、存在するコアすべてを使用します。

% server +RTS -N -RTS

なお、実行バイナリに指定するオプションの最後に、ランタイムオプションを書く場合は、最後の -RTS は省略可能です。

% server +RTS -N

IOマネージャの性能の計測

IOマネージャがどれくらい改良されたかを示すために、GHC 7.6.3 と GHC 7.8.4 でコンパイルされたサーバプログラムの性能を比較してみました。計測のために用いた環境は、以下の通りです。

  • 実コアを 20 個持つサーバ機 2 台
    • それぞれ、Xeon E5-2650Lv2 (1.70GHz/10コア/25MB) x 2、メモリ 16GB、ハイパースレッドは未使用
  • OS は両方とも CentOS 7.0
  • 20G のネットワークで直接接続
    • 10Gを2本束ねている

計測ソフトウェアはweighttpを用いて、以下のようなパラメータを指定し、スループット(req/s)を計測しました。ダウンロードする "index.html"の大きさは 612バイトです。

% weighttp -n 100000 -c 1000 -k -t 16 http://IPアドレス/

サーバプログラムとしては、筆者が開発しているwittyを使用ました。wittyは、GHCのランタイムやライブラリに潜むボトルネックを客観的に示すためのプログラムです。コマンドラインオプションを指定することで、筆者が知っているボトルネックを回避するようになります。今回は以下のようなオプションを指定しました。詳しいことは説明しませんが、これで IO マネージャ自体の性能を測定できます。

% witty -a -m -r 8080 +RTS -A32m

"-A32m" は、ガベージコレクタが使うメモリの大きさを 32Mバイトに指定します。経験的に、マルチコアで動くサーバでは、これぐらいの数値がメモリ使用量に対する性能が適切なようです。

witty は、GHC 7.6.3 と GHC 7.8.4 の両方でコンパイルした2つの実行バイナリを利用するのに加えて、2つの使い方をしました。

1つは、ランタイムオプション "-N" でコア数を指定します。これが一般的な使い方です。もう1つは、witty の "-n" オプションを指定します。witty は、このオプションで指定された数だけ(nginx のように)preforkします。

今回はコア数として、1、2、4、8、16 を指定しました。その結果を以下のグラフに示します。

まず、一般的な使い方である紫と赤の線を比較してください。GHC 7.6.3 がまったくスケールしないのに対して、GHC 7.8.4がスケールしているのが分かると思います。これが、我々の研究成果です。

緑の線は、prefork を用いれば GHC 7.6.3 でもマルチコアでスケールするサーバを実装できることを示しています。ただ、prefork を使わなくても、GHC 7.8.4 でコンパイルして、一般的な使い方をすればよいことも分かると思います。

青と赤の線の差は、改良された IO マネージャに未だに残るオーバーヘッドを示しています。解決できるか分かりませんが、筆者の課題リストに積んであります(ガベージコレクタのスケーラビリティに難があるのかもしれません)。

実用的なWebサーバの計測

次は、実用的なWebサーバの性能を計測してみましょう。今回は、3つのWebサーバを計測してみました。

1つ目は、WAI(Web Application Interface)の HTTP エンジンであるWarpです。WAIアプリとして、受け取ったHTTPリクエストを無視し、"Hello, world!"という文字列をHTTPレスポンスとして返すプログラムを書き、Warpにリンクしました。このWAIアプリのコードを以下に示します。

{-# LANGUAGE OverloadedStrings #-}
import Network.HTTP.Types (status200)
import Network.Wai (responseLBS)
import Network.Wai.Handler.Warp (run)
import Control.Concurrent
main :: IO ()
main = runInUnboundThread $ run 8080 app
  where
    app _ respond = do
        respond $ responseLBS status200 [("Content-Type", "text/plain")]
                  "Hello, world!" 

このWAIアプリは、分岐もなく、メモリにしかアクセスしませんから、Warp自体の性能を示すと考えられます。なお、Warp は Web アプリケーションフレームワークである Yesod や Scotty で利用されています。ちなみに筆者は、Warp の開発チームで性能向上を担当しています。

次のWebサーバは、筆者が開発している Web サーバ Mightyです。Mighty は、WAIアプリの1つとして実装しているので、Warp にリンクされます。Mighty には、筆者のサイトを運用するのに必要な機能、例えば静的コンテンツの提供、CGI、リバースプロキシなどが実装されています。

最後のWebサーバは、nginxです。

公平な比較になるよう、Mighty と nginx をできるだけ同じように振る舞うよう設定しました。例えば、両者ともログを取らず、またファイル記述子を再利用するようにしました。

計測環境と計測方法は前述の通りです。Warp と Mighty は GHC 7.8.4 でコンパイルし、ランタイムオプション -N でコア数を指定しました。nginx は、"worker_processes" で prefork するワーカの数を指定しました。

以下に、計測結果のグラフを示します。

これは、ある環境で、ある計測ツールのあるパラメータを使用するとこうなっただけであり、このグラフのみでとやかく言うつもりはありません。nginxは、Mightyよりもはるかに高機能な Web サーバなので、比較自体に意味があるのかも分かりません。

とにかく、Haskellでも高性能なサーバが(簡潔に)書けること、7.8.1 以降の GHC を使えば、そのサーバはマルチコア環境でスケールすることを感じ取っていただければ幸いです。

もし、IOマネージャの実装に興味がある人がいるようでしたら、また別の機会に説明したいと思います。

執筆者プロフィール

山本 和彦(やまもと かずひこ)

株式会社IIJイノベーションインスティテュート(IIJ-II)技術研究所 主幹研究員
1998年IIJ入社。開発した代表的なオープンソフトに Mew、Firemacs、Mighty がある。「プログラミング Haskell」や「Haskellによる並列・並行プログラミング」の翻訳者。職場では Haskell、家庭では3人の子供と格闘する日々を送っている。

関連リンク

IIJ Technical WEEK 2014 講演資料 「マルチコア時代の最新並列並行技術~ Haskellから見える世界 ~」 [289KB]PDF
本格的にマルチコア時代が到来した今、プログラマにとって並列並行技術は大きな関心事です。プログラムを並列並行化する際には、昔からロックとOSスレッドが使われており、今なお主流であり続けています。この2つの技術は、ほとんどの並列並行プログラムを実現できるほど汎用的ですが、同時に低水準過ぎて間違いを起こしやすく、プログラマを苦しめています。本セッションでは、関数型言語Haskellが提供する最新で高水準で実践的な並列並行技術をご紹介します。(2014年11月26日)
最新の技術動向 「高速WebサーバMighttpdのアーキテクチャ」
IIJ-II技術研究所では、2009年の秋からMighttpd(mightyと読む)というWebサーバの開発を始め、オープンソースとして公開しています。この実装を通じて、マルチコアの性能を引き出しつつ、コードの簡潔性を保てるアーキテクチャにたどり着きました。ここでは、各アーキテクチャについて順を追って説明します。(2012年5月29日)
「マルチコア時代のサーバプログラミングとHaskell」 PDF
"Mighttpd - a High Performance Web Server in Haskell"PDF
Mighttpdの実装に関する記事が掲載されています。

ページの終わりです

ページの先頭へ戻る