<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>O&apos;Reilly Japan Community Blog</title>
    <link rel="alternate" type="text/html" href="http://www.oreilly.co.jp/community/blog/" />
    <link rel="self" type="application/atom+xml" href="http://www.oreilly.co.jp/community/blog/atom.xml" />
    <id>tag:www.oreilly.co.jp,2008-10-17:/community/blog//4</id>
    <updated>2012-02-03T06:05:55Z</updated>
    <subtitle>このblogには、オライリーWebサイト協力者からの寄稿記事や、独自のインタビュー、レポート記事などを掲載します。</subtitle>
    <generator uri="http://www.sixapart.com/movabletype/">Movable Type 4.13</generator>

<entry>
    <title>Webサービスにおける外部APIの使用(その1) - キャッシュ</title>
    <link rel="alternate" type="text/html" href="http://www.oreilly.co.jp/community/blog/2012/02/using-api-on-web-service-part1.html" />
    <id>tag:www.oreilly.co.jp,2012:/community/blog//4.947</id>

    <published>2012-02-03T05:30:00Z</published>
    <updated>2012-02-03T06:05:55Z</updated>

    <summary>ここ最近のWebサービスでは、Googleを始めとする他のサービスが提供するAPIを使用して、そのデータを活用するというのが当たり前になってきており、また、そのためのライブラリ等の基盤も整っています。...</summary>
    <author>
        <name>Kazuo Kashima</name>
        <uri>http://retty.me/</uri>
    </author>
    
        <category term="Contributors Post" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="ja" xml:base="http://www.oreilly.co.jp/community/blog/">
        <![CDATA[<p class="lead">ここ最近のWebサービスでは、Googleを始めとする他のサービスが提供するAPIを使用して、そのデータを活用するというのが当たり前になってきており、また、そのためのライブラリ等の基盤も整っています。今回はそうしたライブラリの使い方からちょっとだけ先に進んで、外部APIのアクセス数制限を回避するためのキャッシュについて説明します。</p>
<div class="section" id="introduction">
<h1>はじめに</h1>
<p>はじめまして。Retty株式会社の鹿島と申します。今回からこのblogに記事を書かせていただくことになりました。今後、読者の皆さんのご意見なども取り入れつつ、何か役に立つような内容を書いていければと思っていますので、よろしくお願いします。</p>
<p>この記事の内容ですが、我々が開発しているRettyというWebサービスでの実例を通じて、教科書にはあまり載っていないtips、落とし穴等を紹介したいと思います。対象読者として以下のような方を想定しています。</p>
<ul class="simple">
<li>Webサービス開発に興味がある人</li>
<li>これからはじめようと思っている人（比較的初心者の方）</li>
</ul>
<p>具体的には以下のような経験があることを前提としています。</p>
<ul class="simple">
<li>（言語を問わず）プログラミングの経験</li>
<li>Facebook等のWebサービスの使用経験</li>
</ul>
<p>このサイトに来ている方の大多数は、この前提をクリアしているだろうと思います。</p>
</div>
<div class="section" id="retty">
<h1>Rettyのサービス概要</h1>
<div class="figure">
<img alt="Rettyのサービスページ" src="./retty-01.png" style="width: 300px;">
<p class="caption">Rettyのサービスページ</p>
</div>
<p>まずは、我々が開発しているRettyというサービスについて少し紹介させて下さい。<a class="reference external" href="http://retty.me/">Retty</a>は「行ったお店を共有する、記録するソーシャルグルメサイト」です。具体的な機能は以下のようになります。</p>
<ul class="simple">
<li>行ったお店のレビューを投稿する</li>
<li>「友達」や「嗜好の合う人」をフォローして、その人達の投稿をタイムライン（TL）上で閲覧する</li>
<li>行きたい店があった場合には、行きたいリストに追加する</li>
<li>スマートフォンで、現在地付近の人気のお店、行きたいリストに追加したお店を検索する</li>
</ul>
<p>サービスはWeb、iPhone、Androidのマルチプラットフォームで展開していますが、以下のように使い分けている方が多いようです。</p>
<ul class="simple">
<li>TLの閲覧やお店の行きたいリストへの追加 → Web</li>
<li>現在地付近のお店検索                   → スマートフォン</li>
<li>投稿                                   → Web、スマートフォン両方</li>
</ul>
</div>]]>
        <![CDATA[<div class="section" id="web">
<h1>Webサービス開発において特に必要なこと（技術者の視点から）</h1>
<p>今回は最初なので、まず「Webサービス開発で特に必要なこと」について技術者の視点から考えてみたいと思います。</p>
<p>まず、これはスタートアップ企業全般に言えることですが、人的リソースがとにかく足りません。Rettyは3人（＋サポートメンバー）の技術者で開発をしています。比較的恵まれているようにも思えますが、Web、iPhone、Androidと3つのプラットフォームを同時に展開しているので、実際にはあまり余裕がありません。</p>
<p>また、Webサービスの世界は動きが速いため、競合サービスが数多く登場します。競合するサービスに負けないため、現在のシステムの良いところ、悪いところを的確に把握し、素早く改善する必要があります。受託開発や業務系システムと大きく異なるのがこの点だと思います。</p>
<p>このような状況下で重要なことは、以下のような点だと考えています。</p>
<ol class="arabic">
<li><dl class="first docutils">
<dt>ユーザー行動の把握</dt>
<dd><p class="first last">サイトの素早い改善のためには「ユーザーがどのような行動をしたのか」、もう少し具体的に書くと「どの機能がよく使われているか・使われていないか」「どの機能がユーザーのサイト利用を促進しているか」というような事柄について把握しておく必要があります。これ以外にも、ユーザーからフィードバックを得るチャネルを数多く用意しておくことも大切だと感じています。</p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt>重要な機能の開発に注力</dt>
<dd><p class="first last">ユーザーが増えてくるとさまざまな要望をいただきます。また内部でも「こんな機能を追加してはどうか」というアイディアが数多く出てきます。そんな中で自分達の方向性に合致する重要な機能だけを選んで実装し、時にはそれまでに実装したものを捨てる勇気が必要になります。</p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt>外部サービス・APIの活用</dt>
<dd><p class="first last">リソースが限られており、自分達だけでできない部分も多いので、前述の通り必要に応じて外部サービス・APIを使用します。とはいえ、コア機能は外部サービスに頼らないで自分達で作る必要もあります。</p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt>フレームワーク等の活用</dt>
<dd><p class="first last">これは一般的なことなので、ここでは詳しく書きません。</p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt>短いリリースサイクル</dt>
<dd><p class="first last">この点に関してもあまり異論はないかと思います。Rettyでは大きめのアップデートは1～2週間に1度、小さな修正は随時実施しています。</p>
</dd>
</dl>
</li>
</ol>
<p>今回はこれらのうち「外部APIの活用」について書いていきます。</p>
</div>
<div class="section" id="api">
<h1>外部APIの活用</h1>
<p>ここ最近のほとんどのWebサービスが何らかの形で外部のAPIを使用しています。大雑把に分類したものが以下の表1です。ここに書いた以外にも多数のサービスがAPIを提供しており、有名なサービスであればほとんど全てが何らかの形でAPIを公開しているのではないでしょうか。</p>
<p>表1：主なWebサービスが外部に提供しているAPI</p>
<table border="1" class="docutils">
<colgroup>
<col width="27%">
<col width="44%">
<col width="28%">
</colgroup>
<thead valign="bottom">
<tr><th class="head">&nbsp;</th>
<th class="head">主な機能</th>
<th class="head">主なサービス提供者</th>
</tr>
</thead>
<tbody valign="top">
<tr><td>SNS系</td>
<td>認証（OAuth）
ソーシャルグラフの取得
（フォロー、フレンドの関係）
ユーザーコンテンツの作成、取得
（日記、写真など）</td>
<td>Facebook、Twitter、
mixi、foursquare</td>
</tr>
<tr><td>EC系</td>
<td>商品情報の検索、アフィリエイト</td>
<td>Amazon、 楽天</td>
</tr>
<tr><td>情報提供、検索系</td>
<td>データの検索、データの表示</td>
<td>Google、Yahoo、はてな</td>
</tr>
<tr><td>ユーザーコンテンツ系</td>
<td>データの登録（写真、ドキュメント）
データの取得、表示</td>
<td>Google Docs、Flickr、
instagram、evernote</td>
</tr>
</tbody>
</table>
<p>以下、Rettyで使っているものを中心にいくつか紹介していきます。</p>
<div class="section" id="facebook">
<h3>Facebook</h3>
<p>最近のWebサービスでは、サーバー側にユーザー情報を直接持たず、OAuthという仕組みを使用してFacebookやTwitterなどのアカウントと連動することで認証を行うタイプのサービスが数多くあります。思いつくままにいくつか挙げると、foursquare、Yelp、Wondershake、ソーシャルランチなどがあります。
認証以外でのFacebookの主な使用法としては、</p>
<ul class="simple">
<li>ユーザーの基本情報(氏名、プロフィール文章等)の取得</li>
<li>ウォールへの書き込み</li>
<li>メッセージ送信</li>
</ul>
<p>などが挙げられます。基本的には、ユーザーがFacebook上でできることは、ほとんどがアプリケーションからも実行可能です。詳細は<a class="reference external" href="http://developers.facebook.com/docs/reference/api/">Facebook APIのドキュメント</a>を参照して下さい。</p>
<p>RettyではFacebook APIを用いて、OAuthによる認証、ユーザー情報の取得、フレンドの取得、ウォールへの書き込み等を行っています。</p>
</div>
<div class="section" id="twitter">
<h3>Twitter</h3>
<p>FacebookではなくTwitterと連動するWebサービスも数多く存在します。Twitter APIでできることはFacebookとほぼ同様で、以下のものです。</p>
<ul class="simple">
<li>認証(OAuth)</li>
<li>ユーザーの基本情報、フォロー、フォロワーの取得</li>
<li>ツイート</li>
</ul>
<p>Twitterと連動しているサービスには、例えばATNDやLivlisなどがあります。Rettyでは、Facebookと同様にTwitterも使用しています。</p>
</div>
<div class="section" id="google-maps">
<h3>Google Maps</h3>
<p>詳しい説明は必要ないと思いますが、Google Maps APIは非常に多くのサービスで使用されています。Rettyではお店の位置を表示するために使用しています。</p>
</div>
<div class="section" id="amazon">
<h3>Amazon</h3>
<p>Amazonでは書籍をはじめとする多種多様な商品を扱っています。商品情報DBの検索やアフィリエイトなどにAPIを使用できます。</p>
</div>
<div class="section" id="foursquare">
<h3>foursquare</h3>
<p>最近では位置情報を使用した新しいサービスが増えていますが、foursquareのAPIを使用しているサービスもそれに伴い増えています。<a class="reference external" href="https://foursquare.com/apps/">4SQ App Gallery</a>でそれらを見ることができます。</p>
<p>Rettyではお店情報の検索にfousquareのAPIを使用しています。</p>
<p>外部APIとただ連携するだけであれば、最近では情報・ライブラリが豊富に揃ってきているのでそれほど難しくありません。ただ、書籍やWebサイトなどであまり触れられていない問題もいくつかあります。今回はそのひとつとしてrate limitsについて書いていきます。</p>
</div>
</div>
<div class="section" id="rate-limits">
<h1>rate limitsとは</h1>
<p>各種サービスのAPIドキュメントを読んだことがある方はご存知かと思いますが、各APIによって投げられるリクエストの数に制限があります。その制限を超えると</p>
<ul class="simple">
<li>APIが一時的に使用不可となる</li>
<li>（頻繁にlimitを超えると）以後のアクセスが拒否される</li>
</ul>
<p>など、サービス継続にとって致命的な事態となります。</p>
</div>
<div class="section" id="id3">
<h2>rate limits対策</h2>
<p>サービス継続にとって致命的なrate limit超過を避けるには何か対策を施す必要がありますが、Rettyでは主に以下の2つの対策をとっています。</p>
<ul class="simple">
<li>キャッシュ（cache）</li>
<li>クライアントからのAPI呼び出し</li>
</ul>
<p>今回はこのうち「キャッシュ」に焦点を当ててお話していきます。</p>
<p>外部APIにおけるキャッシュ使用とは以下のようなことを指します。</p>
<ul class="simple">
<li>外部APIを呼び出して返って来た結果をシステム内部のキャッシュに保存</li>
<li>「同じリクエスト」があった場合は外部APIを再度呼び出さずに内部のキャッシュに保存した結果を返す</li>
</ul>
<p>基本的にはリクエストに対するレスポンスをキャッシュに保存しておき、「同じリクエスト」が来た場合にはキャッシュに保存したデータを返します。</p>
<p>キャッシュを使用するメリットとしては以下の2つです。</p>
<ul class="simple">
<li>外部API呼び出し回数の低減</li>
<li>レスポンスタイムの低減（外部API呼び出しにかかる時間 &gt; キャッシュからの読み出し時間）</li>
</ul>
<p>今回はRettyにおけるfoursquare検索結果のキャッシュを例に、説明していきます。</p>
<div class="section" id="id4">
<h1>キャッシュの設計</h1>
</div>
<div class="section" id="id5">
<h2>保存場所、実装方式の選択</h2>
<p>キャッシュしたデータを保存する場所・バックエンドとしては以下のようなものが挙げられます。</p>
<ul class="simple">
<li>メモリ</li>
<li>ファイル</li>
<li>リモートのサーバー</li>
</ul>
<p>また、キャッシュの実装方式としても以下のようなものが挙げられます。</p>
<ul class="simple">
<li>自作ライブラリ（単純な例では連想配列を使用）</li>
<li>ライブラリ（各種バックエンドを意識せずに扱える）</li>
<li>DBサーバー等の汎用データストア</li>
<li>memcached等のキャッシュ専用サーバー</li>
</ul>
<p>これらの選択の基準としては色々ありますが、代表的なものとして以下を挙げておきます。</p>
<ol class="arabic simple">
<li>機能</li>
<li>性能</li>
<li>ライブラリ・モジュールが簡単に使用可能か(インストールが容易か、プログラムが楽か)</li>
<li>サーバーが簡単に使用可能か(パッケージが用意されている、インストール済み等)</li>
</ol>
<p>Rettyではこのうち3、4を重視し、現在は以下の2つをキャッシュに使用しています。</p>
<ul class="simple">
<li>Zend FrameworkのZend Cacheモジュール＋APC（Alternative PHP Cache）</li>
<li>MySQL</li>
</ul>
<p>今回、例に挙げているfoursquareの結果のキャッシュでは後者を使用しています。APCに関しては次回以降で書こうと思います。</p>
</div>
<div class="section" id="id6">
<h2>キャッシュのキーと内容</h2>
<p>次にキャッシュを検索する際のキーと、キャッシュする内容について説明します。先ほど「『同じリクエスト』があった場合は外部APIを再度呼び出さずに内部のキャッシュに保存した結果を返す」と書きましたが、何をキーにして「同じリクエスト」と判定すれば良いのでしょうか。
まずはキャッシュするデータに関して考えてみます。Rettyでは以下のような場面でfoursquare APIを使用しています。</p>
<ul class="simple">
<li>スマートフォン → 現在地周辺のお店検索</li>
<li>Web            → エリアとキーワードによるお店検索</li>
</ul>
<p>foursquareに投げるリクエストとしてはそれぞれ以下のようになります。</p>
<ul>
<li><dl class="first docutils">
<dt>スマートフォン</dt>
<dd><ul class="first last simple">
<li>現在地の情報（lat、lng）<a class="footnote-reference" href="#id8" id="id7">[1]</a></li>
<li>キーワード（オプション）</li>
</ul>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt>Web</dt>
<dd><ul class="first last simple">
<li>エリア・駅の位置情報（lat、lng）</li>
<li>キーワード</li>
</ul>
</dd>
</dl>
</li>
</ul>
<table class="docutils footnote" frame="void" id="id8" rules="none">
<colgroup><col class="label"><col></colgroup>
<tbody valign="top">
<tr><td class="label"><a class="fn-backref" href="#id7">[1]</a></td><td>lat=latitude=緯度、lng=longitude=経度</td></tr>
</tbody>
</table>
<p>以下の擬似コードのように、lat、lng、キーワードの3つの組み合わせをキャッシュのキーとしてその結果を返す方法が考えつきます。</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">$key = "$lat:$lng:$keyword";
//キャッシュの読み出し
if (($result = getMyCache($key)) === false) {
  //キャッシュなしの場合・・・
  //foursquareのAPIを呼び出して
  $result = callFoursquareAPI($lat, $lng, $keyword);
  //結果をキャッシュに保存
  saveCache($key, $result);
}
return $result;
</pre></div>
<div class="figure align-right">
<img alt="キャッシュの有無をチェックする際ある程度の幅を持たせて検索する" src="./retty-02.png" style="width: 250px;">
<p class="caption">キャッシュの有無をチェックする</p>
</div>
<p>この方法で単純に実装してしまうと、スマートフォンの場合に期待したようには上手く動作しません。なぜかと言うと、全く同じ位置（=同じlat、lng）からお店検索が呼び出されることはほとんどないため、キャッシュにヒットすることもほとんどないからです。</p>
<p>そのため、キャッシュの有無をチェックする際にlat、lngにある程度の幅を持たせて検索する必要があります。例えば、100m以内で実行した検索結果があればそれを返す、といった感じです。</p>
<p>またキーワードに関しては、それを単純にキーにしてしまうと「焼肉」と「焼き肉」が違うキャッシュになってしまうという問題があります、Rettyでは、全文検索エンジンであるSolrと組み合わせてこの辺を解決していますが、これに関してはここでは触れません。</p>
</div>
<div class="section" id="id9">
<h2>キャッシュする内容</h2>
<p>キャッシュする内容には2通りが考えられます。</p>
<ol class="arabic simple">
<li>APIから返ってきたJSONをそのまま</li>
<li>JSONをパースして、お店情報を1件ずつ個別に</li>
</ol>
<p>用途にもよりますが、今回は前者を採用します。理由は、現在使用している情報（店名、位置情報など）以外の情報（例えばチェックイン数）が必要となった時に対応しやすいからです。</p>
</div>
<div class="section" id="id10">
<h2>設計のまとめ</h2>
<ol class="arabic simple">
<li>キャッシュのキーはlat、lng、キーワード</li>
<li>lat、lngで近くのデータがキャッシュにあればそれを使用する</li>
<li>キャッシュにはfoursquareから返ってきたJSONをそのまま保存する</li>
</ol>
<p>これらの要件のうち、特に2番を実現するためには、機能として位置情報での検索ができるシステムが必要です。幸いにもMySQLには<a class="reference external" href="http://dev.mysql.com/doc/refman/5.1/ja/spatial-extensions.html">Spatial Extensions</a>という機能がありますので、次回にこれを用いた実装例について説明します。</p>
</div>
</div>
<div class="section" id="id11">
<h1>まとめ</h1>
<p>今回は以下について説明しました。</p>
<ul class="simple">
<li>私が開発に携わっているRettyというWebサービスの内容、およびWebサービス開発上で必要と思われる事項</li>
<li>最近のWebサービスで使われている主な外部APIについて</li>
<li>外部APIのアクセス数制限の超過を避けるためのキャッシュの設計</li>
</ul>
<p>次回はMySQL<a class="reference external" href="http://dev.mysql.com/doc/refman/5.1/ja/spatial-extensions.html">Spatial Extensions</a>を使用して、位置情報を検索する方法について説明します。</p>
</div>
]]>
    </content>
</entry>

<entry>
    <title>オライリー・ジャパンのePUBフォーマットを支える制作システム</title>
    <link rel="alternate" type="text/html" href="http://www.oreilly.co.jp/community/blog/2012/01/free-opensouce-softwares-support-orj-epub-titles.html" />
    <id>tag:www.oreilly.co.jp,2012:/community/blog//4.949</id>

    <published>2012-01-31T02:30:00Z</published>
    <updated>2012-01-31T03:39:10Z</updated>

    <summary>オライリー・ジャパンから先日発表されたプレスリリース「ePUBフォーマットによる電子書籍のラインナップを開始します」にあるとおり、弊社トップスタジオはオライリー・ジャパンとの共同事業として、ePUBフ...</summary>
    <author>
        <name>Kenshi Muto</name>
        
    </author>
    
        <category term="Contributors Post" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="ja" xml:base="http://www.oreilly.co.jp/community/blog/">
        <![CDATA[<p class="lead">オライリー・ジャパンから先日発表されたプレスリリース「<a class="reference external" href="http://www.oreilly.co.jp/sales/2012/01/ebook-only-title-coming-up-soon.html">ePUBフォーマットによる電子書籍のラインナップを開始します</a>」にあるとおり、弊社トップスタジオはオライリー・ジャパンとの共同事業として、ePUBフォーマットでの電子書籍の制作を開始しました。
トップスタジオではこのePUBフォーマット電子書籍の出版候補の選定、翻訳、編集、そしてePUB制作までに関わっています。本稿では、このePUBの制作プロセスを支えるシステムにフォーカスを当て、その仕組みについて紹介します。</p>

<div class="section" id="id2">
<h1>フリーソフトウェア/オープンソースソフトウェアの集合体としてのシステム</h1>
<p>ePUBの作成にはいろいろな手法がありますが、制作を支えるシステムを構築する上で最も重視したのは、できる限り自動化し、手作業による調整を最小限にするということでした。そのため、このシステムでは原稿を常に最新マスターデータとしてそこから一方向にePUBを作成するバッチ型としており、たとえばSigilなどでePUBを後で細かに手直しする、といった手法は採用していません。</p>
<p>また、将来にわたって保守・改良が制作関係者自身の手でできるよう、中核部分をフリーソフトウェア/オープンソースソフトウェアで構成し、原稿の形式もオープンなものであることも当然の前提です。</p>
<p>さらに、訳者や編集者など制作にかかわる人たち（このシステムのユーザ）は、文章についてはプロフェッショナルであるもののソフトウェアについてはそうではありません（逆もまた真なり、ですね）。新しくツールを導入したことでそれに振り回されないように、ユーザ側から見て新しく導入するものを最小限にし、面倒な部分はできるだけシステムの内部に置くとともに、原稿の文法エラーチェックなどを自動化し、原稿更新に合わせて即座にePUBやHTMLのプレビューもできるようにする必要もありました。</p>
<p>こうした背景からできあがったのが、次の図に示すシステムです。</p>
<div class="figure align-center">

<a class="reference external image-reference" href="./system-desc.png"><img alt="オライリー・ジャパンのePUBを制作しているシステム" src="./system-desc.png" style="width: 550px;" /></a>

<p class="caption">オライリー・ジャパンのePUBを制作しているシステム</p>

</div>
<p>このシステムで採用している技術やソフトウェアの概要をまとめてみます。</p>
</div>]]>
        <![CDATA[<div class="section" id="id2-1">
<ul>
<li><dl class="first docutils">
<dt><a class="reference external" href="http://www.debian.org/">Debian GNU/Linux</a></dt>
<dd><p class="first last">保守性・ソフトウェアの多様性に優れたフリーソフトウェアのOSです（筆者はこのOSの開発元Debian Projectの公式開発者ですから採用は当たり前ですね ;-)）。各書籍のデータを集約し、ePUB制作の自動化を担います</p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt><a class="reference external" href="https://github.com/kmuto/review">ReVIEW</a></dt>
<dd><p class="first last">簡易なマークアップ言語およびその変換器（テキスト、HTML/ePUB、LaTeX、InDesign XMLに変換できます）。ePUB化のための原稿形態の1つとして採用しています。Ruby開発や技術系書籍の執筆者として著名な青木峰郎さんが設計・実装され、現在は筆者や高橋征義さん（達人出版会代表、日本Rubyの会会長）、角正典さん（ワイクル株式会社）が保守・改良を続けています</p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt><a class="reference external" href="http://www.docbook.org/">DocBook</a></dt>
<dd><p class="first last">SGML/XMLベースの構造化文書形式。このXMLバージョンを、ePUB化のための原稿形態の1つとして採用しています。OASISプロジェクトの委員会が保守しています</p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt><a class="reference external" href="http://docbook.sourceforge.net/">dbtoepub</a></dt>
<dd><p class="first last">DocBook XSLTを利用して、XMLファイルからePUBを作成するツール。Rubyで実装されていますが、中核はXSLT側にあります。オライリー・ジャパンの紙面構成に近い読書感が得られるよう、いくぶん改変しています</p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt><a class="reference external" href="http://www.ruby-lang.org/ja/">Ruby</a></dt>
<dd><p class="first last">言わずとしれたプログラミング言語です。システム内のいくつかのフリーソフトウェア/オープンソースソフトウェアでも使われているほか、それぞれのソフトウェアのパイプ役としても利用しています</p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt><a class="reference external" href="http://rubyonrails.org/">Ruby on Rails</a></dt>
<dd><p class="first last">RubyによるMVCフレームワークです。このシステムでは、各書籍のリポジトリ（作業プロジェクト）およびユーザーをWeb上から管理するページを提供するのに使っています。このRailsで管理する範囲の規模は小さく、即応性もさほど要求されないため、利用データベースバックエンドには軽量なSQLiteを使用しています</p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt>dRuby</dt>
<dd><p class="first last">Rubyの分散オブジェクト実装ライブラリです。Railsアプリケーションと特権の必要な操作（Subversionリポジトリやユーザ情報ファイルの作成・変更・削除、Apacheの再起動など）とのつなぎ役として使用しています</p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt><a class="reference external" href="http://www.apache.org/">Apache</a></dt>
<dd><p class="first last">Unix系での事実上の標準と言えるWebサーバです。内部動作しているRailsページやビルド成果物、WiKiを、インターネットを通して共同作業者に公開します</p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt><a class="reference external" href="http://practical-scheme.net/wiliki/wiliki.cgi">Wiliki</a></dt>
<dd><p class="first last">Schemeで書かれたWiKiツールです。自動生成には関係していませんが、制作関係者の情報共有のために運用しています</p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt><a class="reference external" href="http://subversion.apache.org/">Subversion</a></dt>
<dd><p class="first last">サーバ/クライアント型のバージョン管理システムです。仕組みの理解が容易で、Windowsクライアント「TortoiseSVN」を始めとする優れたクライアントソフトウェアが揃っていることから、各書籍はそれぞれこのSubversionの「リポジトリ」として管理されます。何か変化が起きるたびに、「フック」という機構を使い、メーリングリストへの差分通知およびJenkinsの自動ビルドが動くように設定しています</p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt><a class="reference external" href="http://git-scm.com/">Git</a></dt>
<dd><p class="first last">分散型のバージョン管理システムです。現時点では制作工程で分散型を導入するメリットが低く、また制作関係者への教育コストが高いことから、書籍データのリポジトリとしては採用していません（システムとして対応すること自体はそれほど難しくはないはずですが）。現在の用途としては、Railsや内部ツールのバックアップ先として利用しています</p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt><a class="reference external" href="http://jenkins-ci.org/">Jenkins</a></dt>
<dd><p class="first last">継続的インテグレーションツールの代表的存在です。このシステムでは、Subversionのリポジトリに更新があるたび（つまり原稿に修正が入るたび）に、最新のePUBの制作およびプレビュー用のHTMLをビルドし、アップロードします。何らかのミスでビルドに失敗すると、すぐにメーリングリストに通知が飛ぶので、どの時点で壊れたかがすぐにわかります</p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt><a class="reference external" href="http://www.gnu.org/software/make/">GNU Make</a></dt>
<dd><p class="first last">ルールおよび依存関係に基づくバッチ処理ツールで、Jenkinsから呼び出されます。ソースコードやコンパイル済みオブジェクトといったファイルが多数発生してその新旧の判断が重要なソフトウェア開発においては細かなルール設計が大切ですが、このシステムにおいてはほぼ単純なバッチ処理にとどまります</p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt><a class="reference external" href="http://0xcc.net/quickml/">QuickML</a></dt>
<dd><p class="first last">作成・登録・廃止のコストが低いメーリングリストです。書籍ごと（リポジトリごと）にQuickMLメーリングリストが自動で用意され、制作関係者を収容して互いの連絡をしやすくします。Subversionリポジトリの更新差分やJenkinsのエラー通知もここに届きます</p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt><a class="reference external" href="http://trac.edgewall.org/">Trac</a></dt>
<dd><p class="first last">バージョン管理システムと連携したチケット管理ツールです。Subversionリポジトリとセットで自動で用意されます。現時点では少人数による短期の制作作業のため、チケットを実際に使う場面はほぼありませんが、大規模な書籍（紙の書籍）では査読者のコメントなどをチケットとして登録管理することがあります</p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt><a class="reference external" href="https://docs.google.com/">Google Docs</a></dt>
<dd><p class="first last">これだけはフリーソフトウェア/オープンソースソフトウェアではありませんが、オライリー・ジャパンとトップスタジオの間の各書籍の進行状況を一覧表にして、「これは編集が終わっている」「これはカバーのファイル待ち」といったことが一目でわかるようにするという補助的な役割として利用しています。いずれはRails上に同等機能を実装できるとよいのでしょうが、リモート同士で同期して入力・保存がシームレスなテーブルを実現するというのは、このシステムを作るよりもたいへんな作業に思えます</p>
</dd>
</dl>
</li>
</ul>
<p>聞いたことがあるものもあれば、初耳というものもあるでしょうか。いずれにせよ、オライリー・ジャパンのePUBは特別な魔法で生み出されているのではなく、既存のたくさんのフリーソフトウェア／オープンソースソフトウェアの組み合わせと、ちょっとしたスパイスでできているのです。</p>
</div>
<div class="section" id="id3">
<h1>原稿からePUBができるまで</h1>
<p>前記のように、このシステムではePUBを生成するための原稿データ形式として「ReVIEW」と「DocBook」の2つをサポート対象としています。このたび発表した最初のePUB電子書籍ラインナップ3冊のうち、『Flex 4.5によるAndroidアプリケーション開発』『スケーリングMongoDB』がReVIEW、『マネージャーのための仮想化ガイド』がDocBookを採用しています。ある書籍についてどちらの形式を採用するかは、制作関係者の作業スタイルに大きく依存しており、またそれぞれに利点・欠点があります（次表）。</p>
<table border="1" class="docutils">
<caption>ReVIEWの利点と欠点</caption>
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<thead valign="bottom">
<tr><th class="head">利点</th>
<th class="head">欠点</th>
</tr>
</thead>
<tbody valign="top">
<tr><td><ul class="first last simple">
<li>XML対応ツールを持たない/慣れない翻訳者・編集者が、慣れた従来のやり方で翻訳・編集できる</li>
<li>ReVIEWのマークアップ記述（タグ）は簡易で覚えやすい。「ゆるい」フォーマットなので、ある程度適当に書いても適切に変換される</li>
<li>ReVIEWの原稿からInDesign XML（Adobe社の商用DTPソフトウェア「InDesign」にインポートできる形式）やLaTeXに変換して、紙の書籍へもすぐに転換できる！（たとえば紙の書籍『入門ソーシャルデータ』はReVIEWを使って制作された）。もちろん、ReVIEWを使って制作した紙の書籍のePUB化は即実現できる</li>
<li>生成されるHTMLは構造が単純なので、スタイルシートや見栄えの調整フィルタも作りやすい</li>
<li>筆者自身がReVIEWを開発しているので、改変・改良要望に対応しやすい</li>
</ul>
</td>
<td><ul class="first last simple">
<li>原書データにあった要素情報（たとえば「見出し」「太字」「プログラムコード」）が翻訳時に失われるため、改めてReVIEWタグを付け直さないといけない</li>
<li>複雑な表の実現のためには擬似的なタグを入れて後で加工するなどの細工が必要</li>
<li>章単位で管理するため、後から節ごとに分けたePUBファイルを作ることができない</li>
</ul>
</td>
</tr>
</tbody>
</table>
<table border="1" class="docutils">
<caption>DocBookの利点と欠点</caption>
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<thead valign="bottom">
<tr><th class="head">利点</th>
<th class="head">欠点</th>
</tr>
</thead>
<tbody valign="top">
<tr><td><ul class="first last simple">
<li>英語原書データがそもそもDocBook形式なので、要素情報をほぼそのまま利用できる</li>
<li>XSLTで高度かつ柔軟に出力結果の調整ができる</li>
<li>複雑な表を表現できる</li>
</ul>
</td>
<td><ul class="first last simple">
<li>膨大な数のタグがあり、翻訳者・編集者双方に読みにくい。XML対応ツールを使用しない限り、ミスを起こしやすい</li>
<li>ちょっとした入力ミスなどでよくわからないエラーを引き起こしやすい</li>
<li>XSLTを構成するXSLが複雑怪奇で、課題に対してどこを修正すべきか見通しが悪い</li>
<li>商用DTPソフトウェアに適した変換は（現状）簡単には実現できない</li>
</ul>
</td>
</tr>
</tbody>
</table>
<p>まずはReVIEWの場合の工程から説明しましょう。</p>
<div class="figure align-right">
<a class="reference external image-reference" href="./review.png"><img alt="ReVIEWのサンプル" src="./review.png" style="width: 15em;" /></a>
<p class="caption">ReVIEWのサンプル</p>
</div>
<p>翻訳者または編集者は、ReVIEW形式の翻訳原稿を、まずその書籍専用のSubversionリポジトリに登録（コミット）します（SubversionにもReVIEWにも抵抗ない、という翻訳者はまだそういないので、これを行うのはほぼ編集者ですが）。編集作業も同様に、原稿ファイルを修正してはSubversionリポジトリにコミット、という作業を繰り返すだけです。</p>
<p>さて、何らかのコミットが行われると、Subversionリポジトリにあらかじめ設定してあるフックで、制作関係者のメーリングリストにコミット内容（以前のバージョンとの差分）が送られるとともに、CIサーバのJenkinsに通知が届きます。これを受けてJenkinsは、該当リポジトリに付随するMakefileをGNU Makeで実行します。Makefileの中では、ePUBを生成するために、次のような手続きを呼び出します。</p>
<ol class="arabic simple">
<li>ReVIEW形式ファイルを別形式に変換する「review-compile」コマンドを実行し、汎用XHTML形式に変換する</li>
<li>1.で生成されたXHTMLファイルは汎用的なもので、オライリー・ジャパンの電子書籍のデザインとは異なる部分が多いため、フィルタスクリプト（Rubyで記述したもの）に通して加工する</li>
<li>リポジトリにあるメタ情報ファイル（YAML形式のファイル）に従い、カバーや奥付などを生成する</li>
<li>リポジトリに特定の最終調整プログラムが存在するなら、ファイル群をePUBとしてパッキングする直前に、それを実行する（たとえば「カバーの書名の特定箇所で改行する」）</li>
<li>ePUBとしてパッキングする</li>
<li>制作関係者のためのWeb公開領域に、ePUBファイルおよびその展開物をプレビューのために配置する</li>
</ol>
<p>この手続き中に何らかの失敗が起きた場合は、警告とともにその経緯のログが自動でメーリングリストに送付されます。これにより、ユーザはどの時点のコミットで問題が発生したかがすぐにわかるわけです。</p>
<p>続いてDocBookのほうの工程も見てみましょう。</p>
<div class="figure align-right">
<a class="reference external image-reference" href="./docbook.png"><img alt="DocBookのサンプル" src="docbook.png" style="width: 15em;" /></a>
<p class="caption">DocBookのサンプル</p>
</div>
<p>こちらもほぼReVIEWの場合と変わりません。翻訳者または編集者がDocBook形式の翻訳原稿をSubversionリポジトリにコミットし、編集作業ではやはりこの原稿ファイルを修正してコミット、を繰り返します。ただし、DocBook形式はまだ作業者にタグのミスや、当初は想定していなかった要素処理が必要になることもあり、度重なるJenkinsのエラーにユーザが落胆してしまう恐れがあります。そのため、現時点ではJenkinsのビルドを有効化する前に、筆者がまずデータの検証と大まかな調整をかけるようにしています。</p>
<p>コミットに対するSubversionリポジトリのフックで、メーリングリストへの通知と、Jenkinsへの通知が行われ、Jenkinsは次の手続きを行うmakeを実行します。</p>
<ol class="arabic simple">
<li>DocBook形式ファイルをePUB形式に変換する「dbtoepub」コマンドを実行する。オリジナルのdbtoepubコマンドに各種のフックやパラメータ指定の機能を加え、カスタマイズしたものとなっている</li>
<li>dbtoepubコマンドでのePUB形式への変換の実体は、XSLTによるものである。オライリー・ジャパンの電子書籍のデザインに合わせたXSLTのセットを呼び出し、XHTMLへの変換を行う</li>
<li>リポジトリにあるメタ情報ファイルに従い、カバーや奥付などを生成する</li>
<li>リポジトリに特定の最終調整プログラムが存在するなら、ファイル群をePUBとしてパッキングする直前に、それを実行する</li>
<li>ePUBとしてパッキングする</li>
<li>制作関係者のためのWeb公開領域に、ePUBファイルおよびその展開物を配置する</li>
<li>DocBookの場合、原稿のXMLの書き方によっていくつかの問題（リンクテキストが空になるなど）が発生することがあるため、チェッカーを実行して可能性のある問題を検査する</li>
</ol>
<p>こちらも、途中で何か問題があれば経緯のログが自動でメーリングリストに送付されます。</p>
<p>ユーザはこうしてできたePUBファイル、あるいはプレビュー用のHTMLファイルをWebブラウザやePUBリーダで確認し、修正すべき箇所があれば原稿ファイルに手を入れてコミットする、ということになります。そして、読者の皆さんに販売できる品質になったとオライリー・ジャパンが判断した時点で、そのときの最新のePUBファイルがそのまま販売用のePUB電子書籍商品となるわけですね。</p>
</div>
<div class="section" id="id5">
<h1>まとめ</h1>
<p>いかがでしたでしょうか。「ReVIEWあるいはDocBook形式の原稿ファイルからePUBファイルを作る」という工程だけ見れば自動化は果たせたものの、選定、翻訳、編集といった大半の部分は、現代のテクノロジーでは（そしておそらく今後とも）人間の手が必要です。今後のePUBフォーマット電子書籍制作を継続させていくためにも、シリーズの順調な販売を祈りつつ、読みやすさのさらなる向上や、ePUBバージョン3への対応なども進めていきたいと思います。本稿やePUB電子書籍についてのご意見・ご感想、お待ちしております！</p>
</div>
<div class="section" id="id6">
<h1>参考</h1>
<p>本稿で紹介したシステムで制作した書籍と、関連する書籍を以下にご紹介します。</p>
<div class="section" id="id7">
<h2>本稿のシステムで制作した書籍</h2>
<div class="figure">
<a class="reference external image-reference" href="http://www.oreilly.co.jp/books/9784873115184"><img alt="Flex 4.5によるAndroidアプリケーション開発" src="http://www.oreilly.co.jp/books/images/picture_small978-4-87311-518-4.gif"></a>
<p class="caption">Flex 4.5によるAndroidアプリケーション開発</p>
</div>
<div class="figure">
<a class="reference external image-reference" href="http://www.oreilly.co.jp/books/9784873115221"><img alt="スケーリングMongoDB" src="http://www.oreilly.co.jp/books/images/picture_small978-4-87311-522-1.gif"></a>
<p class="caption">スケーリングMongoDB</p>
</div>
<div class="figure">
<a class="reference external image-reference" href="http://www.oreilly.co.jp/books/9784873115214"><img alt="マネージャーのための仮想化ガイド" src="http://www.oreilly.co.jp/books/images/picture_small978-4-87311-521-4.gif"></a>
<p class="caption">マネージャーのための仮想化ガイド</p>
</div>
<div class="figure">
<a class="reference external image-reference" href="http://www.oreilly.co.jp/books/9784873115139/"><img alt="入門 ソーシャルデータ" src="http://www.oreilly.co.jp/books/images/picture_small978-4-87311-513-9.gif"></a>
<p class="caption">入門 ソーシャルデータ</p>
</div>
</div>
<div class="section" id="id8">
<h2>関連書籍</h2>
<div class="figure">
<a class="reference external image-reference" href="http://www.oreilly.co.jp/books/4873112699/"><img alt="GNU Make 第3版" src="http://www.oreilly.co.jp/books/images/picture_small4-87311-269-9.gif"></a>
<p class="caption">GNU Make 第3版</p>
</div>
<div class="figure">
<a class="reference external image-reference" href="http://www.oreilly.co.jp/books/9784873114385/"><img alt="Head First Rails" src="http://www.oreilly.co.jp/books/images/picture_small978-4-87311-438-5.gif"></a>
<p class="caption">Head First Rails</p>
</div>
<div class="figure">
<a class="reference external image-reference" href="http://www.oreilly.co.jp/books/9784873113678/"><img alt="初めてのRuby" src="http://www.oreilly.co.jp/books/images/picture_small978-4-87311-367-8.gif"></a>
<p class="caption">初めてのRuby</p>
</div>
<div class="figure">
<a class="reference external image-reference" href="http://www.oreilly.co.jp/books/9784873114149/"><img alt="実用 Subversion 第2版" src="http://www.oreilly.co.jp/books/images/picture_small978-4-87311-414-9.gif"></a>
<p class="caption">実用 Subversion 第2版</p>
<dl class="footnote docutils">
<dt>武藤健志</dt>
<dd>株式会社トップスタジオ執行役員。編集業務のほか、自動DTPシステム構築、社内インフラ管理、技術コンサルティング、3時のおやつ作り、その他諸事雑用と手を広げて自分の首を絞めている。Debian Project公式開発者だが、最近その作業に充てる時間を取れないのが悩み。Twitter: <a class="reference external" href="https://twitter.com/kmuto">&#64;kmuto</a></dd>
</dl>
</div>
</div>
</div>]]>
    </content>
</entry>

<entry>
    <title>Python の名前空間とスコープ</title>
    <link rel="alternate" type="text/html" href="http://www.oreilly.co.jp/community/blog/2011/11/namespace-and-scope-in-python.html" />
    <id>tag:www.oreilly.co.jp,2011:/community/blog//4.901</id>

    <published>2011-11-21T02:30:00Z</published>
    <updated>2011-11-21T02:36:54Z</updated>

    <summary> プログラムのロジックを考え、実装を行う上で、変数の名前空間やスコープはとても重要です。 これらはロジックを組み立てる上での複雑さに直結し、ソースコードの読みやすさにダイレクトに関係してくるためです。...</summary>
    <author>
        <name>Shoma Hosaka</name>
        
    </author>
    
        <category term="Contributors Post" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="ja" xml:base="http://www.oreilly.co.jp/community/blog/">
        <![CDATA[<div class="lead">
<p>プログラムのロジックを考え、実装を行う上で、変数の名前空間やスコープはとても重要です。
これらはロジックを組み立てる上での複雑さに直結し、ソースコードの読みやすさにダイレクトに関係してくるためです。
この記事では、私が Python で開発をする上で気をつけるようにしている名前空間やスコープに関するお話をします。</p>
</div>
<div class="section" id="id1">
<h3>対象環境</h3>
<p>この記事では、 Python 2.7.2 でソースコードを実行して確認しています。</p>
</div>
<div class="section" id="id2">
<h3>コーディングスタイルについて</h3>
<p>名前空間やスコープの前に、まずは基本的なコーディングスタイルについて軽くお話しします。</p>
<p>Python のコーディングスタイルというと、 <a class="reference external" href="http://www.python.org/dev/peps/pep-0008/">PEP 8 &#8211; Style Guide for Python Code</a> (日本語訳は <a class="reference external" href="http://oldriver.org/python/pep-0008j.html">こちら</a> )が有名です。
これは、 Python でプログラムを書く上で守っておくとよいお作法について書かれており、 Python のコーディングスタイルとしてはデファクトスタンダードといえるでしょう。</p>
<p>この PEP8、例えば以下のようなことが書かれています。</p>
<ul class="simple">
<li>インデントは 4 スペースで</li>
<li>タブとスペースを混ぜて使わない</li>
</ul>
<p>といったような Python らしいインデントの話から</p>
<ul class="simple">
<li>演算子の前後にスペースを入れる</li>
<li>代入演算子を縦にそろえるためスペースを入れることはしない</li>
<li>モジュール・クラス・変数・関数などの名規則</li>
<li>推奨される例外処理方法</li>
</ul>
<p>などの書き方に関することまで広範に及びます。</p>
<p>Python で開発をするのであれば一読しておくことをオススメします。
また、このスタイルを守ると多くの Pythonista にとって読みやすいソースコードになるのではないでしょうか。</p>
</div>
]]>
        <![CDATA[<div class="section" id="id4">
<h3>Python におけるスコープ</h3>
<p>名前空間とスコープは切っても切れない関係です。
変数の名前空間という意味では、スコープにそれぞれの名前空間が存在するからです。</p>
<p>スコープに関して正しく理解をすることで、「どこで定義された変数がどこから見えるのか」ということがわかるようになり、ソースコードを読む際に役立ちます。</p>
<p>Python では、スコープは大まかに言って以下の二種類しかありません。</p>
<ul class="simple">
<li>モジュールのグローバルスコープ</li>
<li>関数のローカルスコープ</li>
</ul>
<p>クラスを定義する際にもスコープが存在すると言えますが、若干性質が違うため割愛します。</p>
<p>他の言語ではこれだけでなく、さらに細かく分けられている場合があります。</p>
<div class="section" id="c">
<h4>C++ でのスコープ</h4>
<p>例えば C++ では、波括弧で囲まれたブロック単位でスコープを作ることができます。
以下のように if 文のブロック内という局所的なスコープが存在します。</p>
<div class="highlight-cpp"><div class="highlight"><pre><span class="k">const</span> <span class="kt">int</span> <span class="n">y</span> <span class="o">=</span> <span class="mi">20</span><span class="p">;</span>

<span class="k">if</span> <span class="p">(</span><span class="n">condition</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// if 文のブロック内のスコープ</span>
    <span class="k">const</span> <span class="kt">int</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">x</span> <span class="o">+</span> <span class="n">y</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="c1">// x が見つからず、エラーになる</span>
</pre></div>
</div>
<p>同様に for, while, do, try 等でもスコープが導入されますし、以下のように波括弧を付けただけであっても局所的なスコープが作られます。</p>
<div class="highlight-cpp"><div class="highlight"><pre><span class="k">const</span> <span class="kt">int</span> <span class="n">y</span> <span class="o">=</span> <span class="mi">20</span><span class="p">;</span>

<span class="p">{</span>
    <span class="k">const</span> <span class="kt">int</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">30</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">x</span> <span class="o">+</span> <span class="n">y</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="c1">// x が見つからないためにエラー</span>
</pre></div>
</div>
</div>
<div class="section" id="id5">
<h4>Python のスコープ</h4>
<p>Python においては、モジュールのスコープと関数のスコープしかないため、以下のようなソースは実行できます。</p>
<div class="highlight-python"><div class="highlight"><pre><span class="k">def</span> <span class="nf">test</span><span class="p">():</span>

    <span class="k">if</span> <span class="n">condition</span><span class="p">:</span>
        <span class="n">x</span> <span class="o">=</span> <span class="mi">20</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="n">x</span> <span class="o">=</span> <span class="mi">30</span>

    <span class="k">print</span> <span class="n">x</span>
</pre></div>
</div>
<p>if のようにブロックを伴う文の中であっても外側と同じスコープであるため、 if のブロック内で初めて出てきた変数名であっても、 if 文が終わった後であっても使えます。
for, while, try 等のその他の制御構文であっても同様です。</p>
<div class="section" id="id6">
<h5>スコープを利用した書き方</h5>
<p>このように、 ブロックの中で変数を定義しても、ブロックを抜けた後に参照できるため、以下のようなイディオムが存在します。</p>
<div class="highlight-python"><div class="highlight"><pre><span class="k">try</span><span class="p">:</span>
    <span class="kn">import</span> <span class="nn">cPickle</span> <span class="kn">as</span> <span class="nn">pickle</span>
<span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
    <span class="kn">import</span> <span class="nn">pickle</span>
</pre></div>
</div>
<p>これは、 cPickle モジュールが使えない環境で pickle モジュールを代わりに使うという書き方です。この例では try でインポートエラーが発生するかどうかを見ていますが、例えば if 文で Windows 環境, Linux 環境, MacOS X の環境であることを調べ、それぞれの環境で別のモジュールを読み込んだり、環境に合わせた処理を行うといったことができます。環境間の差異を吸収するようなコードを書く場合にはよく使います。</p>
<p>このようなブロックの内外で同一スコープであることを利用した書き方は、他の言語から移ってきた人にとっては違和感を覚えるものかもしれません。</p>
</div>
</div>
<div class="section" id="id7">
<h4>スコープ中の名前空間</h4>
<p>Python においては、スコープの範囲が関数単位と大きく、中に含まれる変数の数が増えがちです。
変数の数が増えるということは、一つの関数の中で把握するべき状態の組み合わせが増えるということですので、なるべく細かな単位で関数に分け、それらを呼び出すようにした方がいいでしょう。
また、そうした細かな関数を多く定義する方がユニットテストを書く上でも楽になります。</p>
<p>私の場合は、細かくするときは一つの分岐する処理 for, if ごとに関数で分けることもあり、関数が増えがちです。
あまりに細かな関数が増えてしまうような場合は、次章で紹介する関数中関数を定義することによって、外にその関数を見せないようにするという手も使えます。</p>
</div>
</div>
<div class="section" id="id8">
<h3>関数の中の関数</h3>
<p>Python では、関数の中で関数を定義できます。</p>
<div class="highlight-python"><div class="highlight"><pre><span class="k">def</span> <span class="nf">outer</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>

    <span class="k">def</span> <span class="nf">inner</span><span class="p">(</span><span class="n">y</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">x</span> <span class="o">+</span> <span class="n">y</span> <span class="c"># 外側の環境の x と y を足す</span>

    <span class="k">return</span> <span class="n">inner</span> <span class="c"># outer 関数の中で定義した inner 関数を返す</span>

<span class="n">result</span> <span class="o">=</span> <span class="n">outer</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>

<span class="k">print</span> <span class="n">result</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span> <span class="c">#=&gt; 30</span>
</pre></div>
</div>
<p>outer 関数の中で定義した inner 関数では、 inner 関数のローカルスコープだけでなく、外側にある outer 関数のスコープも参照できます。
また、 outer 関数を呼ぶと inner 関数を生成して返し、 outer 関数を抜けてしまいますが、 return 文で返された inner 関数では生成されたときの outer 関数実行時の環境を持っており、 inner 関数ではその保持された環境を参照します。</p>
<p>このような仕組みは「クロージャ」や「レキシカルスコープ」と呼ばれ、関数型言語などでよく見られます。</p>
<p>このような関数は、関数の中で局所的に使うような map, filter, reduce 等の高階関数に渡すための関数を定義する場合によく使います。</p>
<div class="highlight-python"><div class="highlight"><pre><span class="k">def</span> <span class="nf">list_mul</span><span class="p">(</span><span class="n">items</span><span class="p">):</span>

    <span class="k">def</span> <span class="nf">mul</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">x</span><span class="o">*</span><span class="mi">2</span>

    <span class="k">return</span> <span class="nb">map</span><span class="p">(</span><span class="n">mul</span><span class="p">,</span> <span class="n">items</span><span class="p">)</span>

<span class="k">print</span> <span class="n">list_mul</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">))</span> <span class="c">#=&gt; [0, 2, 4, 6, 8]</span>
</pre></div>
</div>
<p>この程度であれば mul 関数を定義せずに、 lambda x: x*2 と無名関数を使うこともできますが、煩雑な処理が増えてるような場合は関数として定義します。</p>
<p>このように、ネストした関数を定義しておくと、関数のスコープに限定した名前空間ができ、その内部のみで完結するようになるため、ネストの外側のスコープと切り離して考えることができます。</p>
<div class="section" id="id9">
<h4>ネストした関数の使いどころ</h4>
<p>ネストした関数は、上記のような用法の他に主にデコレータの定義などで使われることが多いですが、私の場合は GUI アプリケーション開発などで使っています。</p>
<p>例えば、以下のような <a class="reference external" href="http://www.pyside.org/">PySide</a> のソースコードがあるとします。</p>
<div class="highlight-python"><div class="highlight"><pre><span class="c">#-*- coding:utf-8 -*-</span>

<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">PySide</span> <span class="kn">import</span> <span class="n">QtGui</span> <span class="k">as</span> <span class="n">gui</span><span class="p">,</span> <span class="n">QtCore</span> <span class="k">as</span> <span class="n">core</span>

<span class="n">app</span> <span class="o">=</span> <span class="n">gui</span><span class="o">.</span><span class="n">QApplication</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span>


<span class="n">window</span> <span class="o">=</span> <span class="n">gui</span><span class="o">.</span><span class="n">QFrame</span><span class="p">(</span><span class="bp">None</span><span class="p">)</span>

<span class="n">layout</span> <span class="o">=</span> <span class="n">gui</span><span class="o">.</span><span class="n">QVBoxLayout</span><span class="p">(</span><span class="bp">None</span><span class="p">)</span>

<span class="n">b1</span> <span class="o">=</span> <span class="n">gui</span><span class="o">.</span><span class="n">QPushButton</span><span class="p">(</span><span class="s">&#39;add&#39;</span><span class="p">)</span>
<span class="n">b2</span> <span class="o">=</span> <span class="n">gui</span><span class="o">.</span><span class="n">QPushButton</span><span class="p">(</span><span class="s">&#39;remove&#39;</span><span class="p">)</span>
<span class="n">listw</span> <span class="o">=</span> <span class="n">gui</span><span class="o">.</span><span class="n">QListWidget</span><span class="p">()</span>
<span class="n">text</span> <span class="o">=</span> <span class="n">gui</span><span class="o">.</span><span class="n">QLineEdit</span><span class="p">()</span>

<span class="n">layout</span><span class="o">.</span><span class="n">addWidget</span><span class="p">(</span><span class="n">listw</span><span class="p">)</span>
<span class="n">layout</span><span class="o">.</span><span class="n">addWidget</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
<span class="n">layout</span><span class="o">.</span><span class="n">addWidget</span><span class="p">(</span><span class="n">b1</span><span class="p">)</span>
<span class="n">layout</span><span class="o">.</span><span class="n">addWidget</span><span class="p">(</span><span class="n">b2</span><span class="p">)</span>

<span class="n">window</span><span class="o">.</span><span class="n">setLayout</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>

<span class="n">window</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>

<span class="n">app</span><span class="o">.</span><span class="n">exec_</span><span class="p">()</span>
</pre></div>
</div>
<p>これを実行すると以下のようなリストボックス, テキストボックス, ボタン二つを持つウィンドウが表示されます。</p>
<div class="figure">
<img alt="./images/guisample4.png" src="./images/guisample4.png" />
<p class="caption">サンプルのスクリーンショット</p>
</div>
<p>ここで、「add ボタンをクリックしたらリストにアイテムを追加」、「remove ボタンをクリックしたら選択したアイテムを削除」という動作を付けようと思います。</p>
<p>これを実装する際はクリック時のイベントを定義して、それぞれボタンに適用するという処理を書く必要があります。
これを、ただ関数を定義するのではなく、関数とその中で定義した関数で書くと以下のようになります。</p>
<div class="highlight-python"><div class="highlight"><pre><span class="c">#-*- coding:utf-8 -*-</span>
<span class="k">def</span> <span class="nf">create_event</span><span class="p">(</span><span class="n">itemlist</span><span class="p">,</span> <span class="n">text</span><span class="p">,</span> <span class="n">add</span><span class="p">,</span> <span class="n">remove</span><span class="p">):</span>
    <span class="sd">u&#39;&#39;&#39;</span>
<span class="sd">    リストコントロール、テキストボックス、ボタン二つにイベントを追加する</span>
<span class="sd">    &#39;&#39;&#39;</span>

    <span class="k">def</span> <span class="nf">add_clicked</span><span class="p">():</span>
        <span class="sd">u&#39;&#39;&#39;</span>
<span class="sd">        追加ボタンがクリックされたら入力されたテキストをリストに追加する</span>
<span class="sd">        &#39;&#39;&#39;</span>

        <span class="n">value</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">text</span><span class="p">()</span>
        <span class="n">itemlist</span><span class="o">.</span><span class="n">addItem</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>


    <span class="k">def</span> <span class="nf">remove_clicked</span><span class="p">():</span>
        <span class="sd">u&#39;&#39;&#39;</span>
<span class="sd">        削除ボタンがクリックされたら選択されたアイテムを削除する</span>
<span class="sd">        &#39;&#39;&#39;</span>

        <span class="n">index</span> <span class="o">=</span> <span class="n">itemlist</span><span class="o">.</span><span class="n">currentRow</span><span class="p">()</span>

        <span class="k">if</span> <span class="n">index</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
            <span class="n">itemlist</span><span class="o">.</span><span class="n">takeItem</span><span class="p">(</span><span class="n">index</span><span class="p">)</span>

    <span class="c"># イベントハンドラを追加</span>
    <span class="n">add</span><span class="o">.</span><span class="n">clicked</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">add_clicked</span><span class="p">)</span>
    <span class="n">remove</span><span class="o">.</span><span class="n">clicked</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">remove_clicked</span><span class="p">)</span>
</pre></div>
</div>
<p>そして、以下のように使います。</p>
<div class="highlight-python"><div class="highlight"><pre><span class="n">create_event</span><span class="p">(</span><span class="n">listw</span><span class="p">,</span> <span class="n">text</span><span class="p">,</span> <span class="n">b1</span><span class="p">,</span> <span class="n">b2</span><span class="p">)</span>
</pre></div>
</div>
<p>このように、ただ関数を二つ定義するのではなく、「4つのコントロールに対するイベントハンドラ生成関数」として定義してあげると、これらのコントロールのセットが増えた際にも同じ関数を呼び出すだけでイベントが適用できるため、手間が省けます。
処理を生成する関数を呼び出す、と言った感覚でしょうか。</p>
<p>このように、イベントハンドラの定義と適用を生成関数に閉じこめることで、再利用性の高いコードになります。
また、ロジックと関連するデータが小さな範囲にまとめられるため、見るべき変数が減って、わかりやすくなります。</p>
<p>ただし、このような関数の中で行う処理が増えてきたり、一つの関数が長くなったりするとソースコード上であっちこっちに処理が飛んでしまい、デバッグ時の見通しは悪くなってしまうので注意が必要です。
あまりに長くなるような生成関数を使う場合は、素直にクラス化してまとめた方がよいでしょう。</p>
</div>
<div class="section" id="id10">
<h4>スコープまとめ</h4>
<p>私がプログラムを書く上でスコープを扱う際に気をつけているのは、「スコープを小さく保ち、把握するべき状態数を減らす」ということです。
作るプログラムの規模が大きくなってくればなってくるほどに、このような細かな気配りが効いてくるため、スコープについて意識することはとても重要です。</p>
</div>
</div>
<div class="section" id="id11">
<h3>モジュールインポート</h3>
<p>モジュールのインポートというものは、対象のスコープの名前空間に対して変数を導入するという構文であると捉えることができます。
ここでは、私なりのモジュールのインポートスタイルについて語ってみます。</p>
<div class="section" id="pep8">
<h4>PEP8 でのインポートスタイル</h4>
<p>PEP8 では以下のようなインポートが推奨されています。</p>
<div class="highlight-python"><div class="highlight"><pre><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">sys</span>

<span class="kn">from</span> <span class="nn">subprocess</span> <span class="kn">import</span> <span class="n">Popen</span><span class="p">,</span> <span class="n">PIPE</span>
</pre></div>
</div>
<p>また、以下のようなインポートスタイルは良くないものであるとされています。</p>
<div class="highlight-python"><div class="highlight"><pre><span class="kn">import</span> <span class="nn">os</span><span class="o">,</span> <span class="nn">sys</span>
</pre></div>
</div>
<p>また、モジュールのインポート順序として</p>
<ul class="simple">
<li>標準モジュール</li>
<li>サードパーティモジュール</li>
<li>作成しているライブラリやアプリケーションのモジュール</li>
</ul>
<p>というものが推奨されています。</p>
<p>また、それぞれの種類のインポート文はまとめて記述するような書き方が推奨されます。</p>
<p>以上を踏まえると以下のような書き方を行うことになるでしょう。</p>
<div class="highlight-python"><div class="highlight"><pre><span class="c"># 標準モジュール</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">os</span>

<span class="c"># サードパーティモジュール</span>
<span class="kn">from</span> <span class="nn">lxml</span> <span class="kn">import</span> <span class="n">etree</span><span class="p">,</span> <span class="n">html</span>
<span class="kn">from</span> <span class="nn">zope.pagetemplate</span> <span class="kn">import</span> <span class="n">pagetemplate</span>

<span class="c"># アプリケーション or ライブラリ固有のモジュール</span>
<span class="kn">from</span> <span class="nn">myapp.mymod</span> <span class="kn">import</span> <span class="n">somefunc</span>
</pre></div>
</div>
</div>
<div class="section" id="id12">
<h4>自分流インポートスタイル</h4>
<p>私はインポートの方針は基本的に PEP8 の方針に従っているのですが、若干違う部分もあります。</p>
<div class="section" id="id13">
<h5>モジュール内一括インポート</h5>
<p>まず、私の中では絶対にやってはいけないパターンというものがあります。</p>
<div class="highlight-python"><div class="highlight"><pre><span class="kn">from</span> <span class="nn">module</span> <span class="kn">import</span> <span class="o">*</span>
</pre></div>
</div>
<p>このように、特定のモジュールから一括でインポートするようなことはまずありません。PEP8 ではこのパターンを使用する側への言及はありませんが、無節操にインポートしてモジュールの名前空間を汚してしまうため、あまり推奨されるものではないと思います。
何をインポートするかを明示しないという点で <a class="reference external" href="http://www.python.org/dev/peps/pep-0020/">The Zen of Python</a> の一文にある &#8220;explicit is better than implicit&#8221; にも反しているのではないでしょうか。</p>
<p>また、このようにインポートすると、ソースコード中で出てくる変数・関数・クラスがいったいどのモジュールから来たものなのか、もしくはモジュールレベルで宣言されているものなのかがわかりにくくなります。特に import * が複数あるような場合は凶悪度が格段に上がります。</p>
<p>このようなインポート文は意外なことにライブラリのサンプルコードに書いてあることがあったりして、若干残念な気持ちになります( <a class="reference external" href="http://www.pyside.org/">PySide</a> の <a class="reference external" href="http://developer.qt.nokia.com/wiki/Hello_World_in_PySide">チュートリアル</a> とか...。複数モジュールから import * する例)。</p>
<p>このようなチュートリアルなどではどのモジュールから読み込んだものなのかがわかりやすく書いてある方がいいですよね。</p>
</div>
<div class="section" id="rom-mod-import-n">
<h5>rom mod import n のパターン</h5>
<p>「from mod import n」のようなインポートを書く際にも若干気をつけていることがあります。
それは、「from 〜 import でインポートするのはモジュールまで」ということです。</p>
<p>上記「import *」についても言えることですが、以下のような状態を回避するという意味でこのように書くようにしています。</p>
<p>たとえば、 module.py というファイルに以下のような定義があるとします。</p>
<div class="highlight-python"><div class="highlight"><pre><span class="c"># module.py</span>
<span class="n">values</span> <span class="o">=</span> <span class="p">[</span><span class="s">&#39;aaa&#39;</span><span class="p">]</span>
</pre></div>
</div>
<p>そして、このモジュールを以下のように読み込むソースコード source.py があるとします。</p>
<div class="highlight-python"><div class="highlight"><pre><span class="c"># source.py</span>
<span class="kn">from</span> <span class="nn">module</span> <span class="kn">import</span> <span class="n">values</span>
</pre></div>
</div>
<p>この場合の source.py の values と module.py の values は、 source.py でのインポート時点では同じオブジェクトになっています。
しかし、もし module.values の値が以下のように書き換えられてしまった場合、これ以降に読み込んだ module.values と source.py の values が別のオブジェクトになってしまいます。
そもそもモジュールのトップレベルスコープに存在する変数を書き換えるという行為をあまり行うべきではありませんが、あまり起きてほしくない状況です。</p>
<div class="highlight-python"><div class="highlight"><pre><span class="kn">import</span> <span class="nn">module</span>
<span class="n">module</span><span class="o">.</span><span class="n">values</span> <span class="o">=</span> <span class="p">{}</span>
</pre></div>
</div>
<p>ただ、このポリシに関しては、テストコードに関してはあまり気にしていません。
私は基本的にテストコードはモジュール単位でファイルを分けるようにしているので、テスト対象の関数やクラスなどは from 〜 import で直接インポートしてしまいます。このようにインポートしたとしても、テストコードなので実行単位がとても小さいというのも理由としてあげられます。</p>
<div class="section" id="id16">
<h6>メリット</h6>
<p>このポリシを適用して私が嬉しいと思っていることは、「ソースコードの一部を見る際に参照している変数の出自が絞られる」ことだと思っています。</p>
<p>例えばこのポリシにおいて、あるモジュールの一部分のソースが以下のようなものであったとします。</p>
<div class="highlight-python"><div class="highlight"><pre><span class="k">class</span> <span class="nc">SomeClass</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>

    <span class="c"># 省略</span>

    <span class="k">def</span> <span class="nf">some_method</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">):</span>

        <span class="n">v</span> <span class="o">=</span> <span class="n">y</span> <span class="o">*</span> <span class="n">x</span>

        <span class="k">return</span> <span class="n">function</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">x</span><span class="p">)</span> <span class="o">+</span> <span class="n">mod</span><span class="o">.</span><span class="n">calc</span><span class="p">(</span><span class="n">v</span><span class="p">)</span>
</pre></div>
</div>
<p>このポリシを守っておくと、some_method のスコープの中で . によって属性アクセスしないで書いて参照できるスコープは</p>
<ul class="simple">
<li>関数のローカルスコープ</li>
<li>ネストされた外側のスコープ</li>
<li>モジュールグローバルスコープ</li>
<li>__builtin__ モジュール</li>
</ul>
<p>のみとなり、ソースの一部分を読む際に頭の中にキャッシュしておくべき情報が少なくて済みます。</p>
<p>実装上のメリットというよりも、補完等を全く使わない環境においての読みやすさを念頭に入れた書き方です。</p>
</div>
<div class="section" id="id17">
<h6>デメリット</h6>
<p>ただ、この「from 〜 import でインポートするのはモジュールまで」というポリシに関しては、そうでない場合と比べて若干のデメリットもあります。</p>
<p>第一に記述が長くなることが挙げられます。
これに関しては</p>
<div class="highlight-python"><div class="highlight"><pre><span class="kn">from</span> <span class="nn">outermodule</span> <span class="kn">import</span> <span class="n">innermodule</span> <span class="k">as</span> <span class="n">inner</span>
</pre></div>
</div>
<p>といったように as を使うことで短くなります。</p>
<p>第二に、外部モジュールのオブジェクトを参照するためには . による属性アクセスを行う必要があり、辞書ルックアップが発生してしまうことです。
これに関しては仕方がないとしか言えません。 Python の仕様ですので。</p>
<p>このレベルでの最適化がモジュール単位で必要な状況というのはあまり考えられませんし、モジュールのトップレベルスコープのことを気にするよりも、もっと小さな関数のスコープにおいてモジュール名前空間のオブジェクトをローカル変数で参照させるなどを行って、以下のように辞書ルックアップの回数を減らす方が効果があるのではないかと思います。</p>
<div class="highlight-python"><div class="highlight"><pre><span class="kn">import</span> <span class="nn">os</span>

<span class="c"># 省略</span>

<span class="k">def</span> <span class="nf">func</span><span class="p">():</span>
    <span class="sd">u&#39;&#39;&#39;</span>
<span class="sd">    os.listdir を大量に呼ぶ関数であるとする</span>
<span class="sd">    &#39;&#39;&#39;</span>

    <span class="n">listdir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">listdir</span>

    <span class="c"># ローカル変数 listdir を使って os.listdir を呼び出すようにする</span>
</pre></div>
</div>
<p>それぞれそれほど大きなデメリットとは思っていませんので、あまり気にはしていません。</p>
</div>
</div>
</div>
</div>
    <div class="section" id="id18">
    <h3>まとめ</h3>
    <p>私の書くソースコードの基本的な方針としては、「スコープと名前空間に気を遣うことでソースコードを俯瞰したときに、状態を把握しやすくし、ソースコードを読む効率を上げる」ことであるといえると思います。
    ソースコードは書くよりも読むことが多いため、読みやすくすることが開発効率を上げることにつながります。</p>
    <p>以上、私が Python でソースコードを書く上で気にしているスコープと名前空間に関してお話しました。</p>
    </div>
]]>
    </content>
</entry>

<entry>
    <title>git-flow によるブランチの管理</title>
    <link rel="alternate" type="text/html" href="http://www.oreilly.co.jp/community/blog/2011/11/branch-model-with-git-flow.html" />
    <id>tag:www.oreilly.co.jp,2011:/community/blog//4.892</id>

    <published>2011-11-07T00:30:00Z</published>
    <updated>2011-11-15T10:58:54Z</updated>

    <summary>今回は分散バージョン管理システムgitと共に用いる「ブランチモデル」について紹介していただきます。gitを使ってみて、その高機能さをどう使えば良いか悩まれた方は、ぜひ本稿をご一読ください。gitそのも...</summary>
    <author>
        <name>Kosei Kitahara</name>
        
    </author>
    
        <category term="Contributors Post" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="ja" xml:base="http://www.oreilly.co.jp/community/blog/">
        <![CDATA[<p class="lead">今回は分散バージョン管理システムgitと共に用いる「ブランチモデル」について紹介していただきます。gitを使ってみて、その高機能さをどう使えば良いか悩まれた方は、ぜひ本稿をご一読ください。gitそのものの使い方については解説していませんので、その際には『 <a class="reference external" href="http://www.oreilly.co.jp/books/9784873114408/">実用git</a> 』などの書籍を参考にしてください。</p>
<p>git-flow は Vincent Driessen 氏によって書かれた
<a class="reference external" href="http://nvie.com/posts/a-successful-git-branching-model/">A successful Git branching model</a> (<a class="reference external" href="http://keijinsonyaban.blogspot.com/2010/10/successful-git-branching-model.html">O-Show 氏による日本語訳</a>) というブランチモデルを補助するための git 拡張です。
git-flow を利用する前には、まずこの文章を一読することをおすすめします。
その骨子については、 <a class="reference external" href="http://d.hatena.ne.jp/Voluntas/20101223/1293111549">Voluntas 氏のブログ</a> が参考になります。</p>
<p>git を使うメリットの 1 つは、そのブランチモデルです。しかし gitを使っていると、その高い柔軟性から各開発者ごとにブランチが <strong>分散</strong> しがちであったり、最新バージョンが把握しにくいといった不便を感じることがあります。</p>
<p>このツールを導入することで、git の長所を損なわずに、</p>
<ul class="simple">
<li>分散型バージョンコントロールシステムである git に集中型 (Subversion 等) の長所を取り入れることができる</li>
<li>チーム内でブランチモデルを共通化できる</li>
</ul>
<p>などの利点を得ることが可能です。</p>
<p>分散型に対する集中型の長所は、</p>
<ul class="simple">
<li>リポジトリが 1 つのため、管理が簡単</li>
<li>マスターが 1 つのため、最新バージョンが把握しやすい</li>
<li>コミットすべきリポジトリがわかりやすい。</li>
</ul>
<p>と言われています。これは、リポジトリが 1 箇所にあることの長所であり、短所です。
git-flow では、中央と <strong>みなす</strong> リポジトリ <tt class="docutils literal">origin</tt> を作成することで、この長所を取り入れています。</p>
<blockquote>
<div class="figure">
<img alt="centr-decentr.png" src="http://www.oreilly.co.jp/community/blog/2011/11/images/centr-decentr.png" />
</div>
<p>Git ブランチモデル（ <a class="reference external" href="http://nvie.com/posts/a-successful-git-branching-model/">A successful Git branching model</a> より。Creative Commons BY-SA ライセンス）</p>
</blockquote>
<p>git-flow のブランチモデルを要約すると、</p>
<p><tt class="docutils literal">origin</tt> には <tt class="docutils literal">master</tt>, <tt class="docutils literal">develop</tt> という 2 つの <strong>メインブランチ</strong> を保持します。</p>
<ul class="simple">
<li>master: リリースブランチ。プロダクトとしてリリースするためのブランチ。リリースしたらタグ付けする。集中型でいう trunk、tag。</li>
<li>develop: 開発ブランチ。コードが安定し、リリース準備ができたら master へマージする。リリース前はこのブランチが最新バージョンとなる。</li>
</ul>
<p>各開発者は <tt class="docutils literal">master</tt>, <tt class="docutils literal">develop</tt> の他に、フィーチャーブランチ、リリースブランチ、ホットフィックスブランチという <strong>サポートブランチ</strong> を利用し分散開発します。このブランチは最終的には破棄されます。各ブランチの目的は</p>
<ul class="simple">
<li>フィーチャーブランチ: 機能の追加。 <tt class="docutils literal">develop</tt> から分岐し、 <tt class="docutils literal">develop</tt> にマージする。</li>
<li>リリースブランチ: プロダクトリリースの準備。
機能の追加やマイナーなバグフィックスとは独立させることで、
リリース時に含めるコードを綺麗な状態に保つ（機能追加中で未使用のコードなどを含まないようにする）ことができる。
<tt class="docutils literal">develop</tt> ブランチにリリース予定の機能やバグフィックスがほぼ反映した状態で <tt class="docutils literal">develop</tt> から分岐する。
リリース準備が整ったら, <tt class="docutils literal">master</tt> にマージし、タグをつける。次に <tt class="docutils literal">develop</tt> にマージする。</li>
<li>ホットフィックスブランチ: リリース後のクリティカルなバグフィックスなど、
現在のプロダクトのバージョンに対する変更用。 <tt class="docutils literal">master</tt> から分岐し、
<tt class="docutils literal">master</tt> にマージし、タグをつける。次に <tt class="docutils literal">develop</tt> にマージする。</li>
</ul>
<p>となります。このサポートブランチにより、マージ、コミットすべきブランチを明確化することができます。</p>
]]>
        <![CDATA[<div class="section" id="id1">
<span id="installation"></span><h1>git-flow 環境を構築する</h1>
<p>git-flow のソースコードはgithubのリポジトリ <a class="reference external" href="https://github.com/nvie/gitflow">nvie&#64;github/gitflow</a> に公開されています。</p>
<div class="section" id="mac-os">
<h2>Mac OS へのインストール</h2>
<p>Mac OS では <a class="reference external" href="http://github.com/mxcl/homebrew">homebrew</a> か <a class="reference external" href="http://macports.org/">MacPorts</a> を使って簡単にインストールできます。</p>
<blockquote>
<p>homebrew:</p>
<pre class="literal-block">
$ brew install git-flow
</pre>
<p>MacPorts:</p>
<pre class="literal-block">
$ port install git-flow
</pre>
</blockquote>
</div>
<div class="section" id="linux">
<h2>Linux へのインストール</h2>
<p>Linux へは 利用しているディストリビューションのパッケージ管理システム、
もしくは以下のコマンドによってインストールできます。</p>
<blockquote>
<pre class="literal-block">
$ wget --no-check-certificate -q -O - https://github.com/nvie/gitflow/raw/develop/contrib/gitflow-installer.sh | sudo bash
</pre>
</blockquote>
</div>
<div class="section" id="windows">
<h2>Windows へのインストール</h2>
<p>Windows の場合は git 本体のインストールを <a class="reference external" href="http://www.cygwin.com/">Cygwin</a> 、 <a class="reference external" href="http://code.google.com/p/msysgit/">msysgit</a> どちらで行ったかによってインストール方法が異なります。</p>
<blockquote>
<p>Cygwin:</p>
<pre class="literal-block">
$ wget -q -O - https://github.com/nvie/gitflow/raw/develop/contrib/gitflow-installer.sh | bash
</pre>
</blockquote>
<div class="section" id="msysgit-git-git-flow">
<h3>msysgit による git / git-flow 環境の構築</h3>
<p>git-flow のドキュメントでは、msysgit の利用が推奨されています。
ここでは、 msysgit による git と git-flow 環境の構築手順について詳しく解説したいと思います。</p>
<p>git-flow の導入に必要なソフトウェアは以下の通りです。</p>
<ul class="simple">
<li><a class="reference external" href="http://www.chiark.greenend.org.uk/~sgtatham/putty/">PuTTY</a>: SSHクライアント。SSH 経由で git を利用する場合に必要。OpenSSH で代用が可能</li>
<li><a class="reference external" href="http://code.google.com/p/msysgit/">msysgit</a>: git 本体</li>
<li><a class="reference external" href="http://gnuwin32.sourceforge.net/packages/util-linux-ng.htm">util-linux パッケージ</a>: <a class="reference external" href="http://gnuwin32.sourceforge.net/">GnuWin32</a> プロジェクトが提供している Util-linux パッケージ。
Util-linux パッケージは Linux においてファイルシステム、コンソール、パーティション、カーネルメッセージなどを取り扱うユーティリティプログラムです。
GnuWin32 は GNU のツール群を Windows 環境（32bit）で使えるようにしたものです</li>
<li><a class="reference external" href="https://github.com/nvie/gitflow">git-flow</a>: git-flow 本体</li>
</ul>
<div class="section" id="id3">
<h4>PuTTY のインストール</h4>
<p>github 等 SSH 経由で git を利用する場合に必要となります。</p>
<ol class="arabic">
<li><p class="first"><a class="reference external" href="http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html">PuTTY ダウンロードページ</a> から最新版のインストーラ (執筆時点では 0.6.1) をダウンロードしてインストールしてください</p>
<div class="figure align-center" id="installation-putty-download">
<img alt="images/installation.putty.download.png" src="images/installation.putty.download.png" />
<p class="caption">ダウンロードページ</p>
</div>
</li>
<li><p class="first">PuTTYgen を起動し、SSH のキーペアを作成します。今回は SSH-2 DSA で生成します</p>
<div class="figure align-center" id="installation-putty-keygen">
<img alt="images/installation.putty.keygen.png" src="images/installation.putty.keygen.png" />
<p class="caption">鍵の生成</p>
</div>
<p>&quot;Generate&quot; キーを押し、&quot;Key&quot; フレーム上でしばらくマウスを動かすと鍵が生成できます。
生成が完了したら、&quot;Save publick key&quot;、&quot;Save private key&quot; により、
それぞれ &quot;id_dsa.pub&quot;、&quot;id_dsa.ppk&quot; 等任意の場所・ファイル名で保存します</p>
</li>
<li><p class="first">Pageant を起動し、生成した鍵を登録します</p>
<div class="figure align-center" id="installation-putty-pageant-start">
<img alt="images/installation.putty.pageant.start.png" src="images/installation.putty.pageant.start.png" />
<p class="caption">起動するとタスクトレイにアイコンが表示されます</p>
</div>
<p>タスクトレイのアイコンから Pageant を起動し、&quot;Add Key&quot; で先ほど生成した鍵を登録します</p>
<div class="figure align-center" id="installation-putty-pageant-addkey">
<img alt="images/installation.putty.pageant.addkey.png" src="images/installation.putty.pageant.addkey.png" />
<p class="caption">プライベートキーの追加</p>
</div>
<p>起動時に自動的に Pageant 起動したい場合は、&quot;スタートアップ&quot; に以下のショートカットを追加します。</p>
<blockquote>
<pre class="literal-block">
&quot;C:\Program Files\PuTTY\pageant.exe&quot; &quot;C:\path\to\.ssh\id_dsa.ppk&quot;
</pre>
</blockquote>
</li>
</ol>
<p>github を使いたい場合は、github.com の鍵をキャッシュします。この場合、事前に github 側に公開鍵 (&quot;id_dsa.pub&quot;) を登録しておく必要があります (<a class="reference external" href="http://help.github.com/win-set-up-git/">参考</a>)。</p>
<blockquote>
<pre class="literal-block">
C:\&gt;&quot;\Program Files\PuTTY\plink.exe&quot; git&#64;github.com The server's host key is not cached in the registry. You have no guarantee that the server is the computer you think it is.
The server's rsa2 key fingerprint is:
ssh-rsa 2048 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
If you trust this host, enter &quot;y&quot; to add the key to PuTTY's cache and carry on connecting. If you want to carry on connecting just once, without adding the key to the cache, enter &quot;n&quot;.
If you do not trust this host, press Return to abandon the connection.
Store key in cache? (y/n) y
Using username &quot;git&quot;.
Server refused to allocate pty
Hi Surgo! You've successfully authenticated, but GitHub does not provide shell access.
</pre>
</blockquote>
</div>
<div class="section" id="id6">
<h4>msysgit のインストール</h4>
<ol class="arabic">
<li><p class="first"><a class="reference external" href="http://code.google.com/p/msysgit/downloads/list">msysgit ダウンロードページ</a> から最新版のインストーラ (執筆時点では 1.7.7) をダウンロードしてインストールしてください。ダウンロードファイルは以下の図に示すように Git-&lt;バージョン番号&gt;-&lt;パッケージのステータスと日付&gt;.exe という形式のものを選択します。</p>
<div class="figure align-center" id="installation-msysgit-download">
<img alt="images/installation.msysgit.download.png" src="images/installation.msysgit.download.png" />
<p class="caption">ダウンロードページ</p>
</div>
<div class="figure align-center" id="installation-msysgit-runfromcmd">
<img alt="images/installation.msysgit.runfromcmd.png" src="images/installation.msysgit.runfromcmd.png" />
<p class="caption">実行方法の選択</p>
<div class="legend">
<p>システムパスを追加し、git をコマンドプロンプトから実行できるようにします</p>
</div>
</div>
</li>
<li><p class="first">git が使う ssh を plink.exe にする</p>
<p>システム環境変数へ <tt class="docutils literal">GIT_SSH</tt> を <tt class="docutils literal"><span class="pre">C:\Program</span> Files\PuTTY\plink.exe</tt> として追加します。</p>
<div class="figure align-center" id="installation-msysgit-path">
<img alt="images/installation.msysgit.path.png" src="images/installation.msysgit.path.png" />
</div>
</li>
<li><p class="first">git のエディタを設定する</p>
<blockquote>
<p><tt class="docutils literal"><span class="pre">C:\Program</span> Files\Git\cmd\git.cmd</tt> にエディタの設定を追加します。以下は Vim (the editor) の設定です。</p>
<pre class="literal-block">
&#64;set GIT_EDITOR=&quot;C:\Program Files\Vim\vim73\gvim.exe&quot;
</pre>
</blockquote>
</li>
<li><p class="first">コマンドプロンプトを起動し、git コマンドが使えるか確認します</p>
<blockquote>
<pre class="literal-block">
C:\&gt;git --version
git version 1.7.7.msysgit.1
</pre>
</blockquote>
</li>
</ol>
</div>
<div class="section" id="id8">
<h4>util-linux パッケージのインストール</h4>
<p><a class="reference external" href="http://gnuwin32.sourceforge.net/packages/util-linux-ng.htm#download">util-linux パッケージ ダウンロードページ</a> から最新版のインストーラ (執筆時点では 2.14.1) をダウンロードしてインストールしてください。</p>
<blockquote>
<div class="figure align-center" id="installation-utillinux-download">
<img alt="images/installation.utillinux.download.png" src="images/installation.utillinux.download.png" />
<p class="caption">ダウンロードページ</p>
</div>
</blockquote>
<p>インストールディレクトリは、手順 1 でmsysgitをインストールしたのと同じ <tt class="docutils literal"><span class="pre">C:\Program</span> Files\Git</tt> を選択します。</p>
<p>※ 他のディレクトリにインストールして <tt class="docutils literal">getopt.exe</tt> のみを <tt class="docutils literal"><span class="pre">C:\Program</span> Files\Git\bin</tt> に複製しても良い</p>
<blockquote>
<div class="figure align-center" id="installation-utillinux-path">
<img alt="images/installation.utillinux.path.png" src="images/installation.utillinux.path.png" />
<p class="caption">インストールディレクトリの選択</p>
</div>
</blockquote>
</div>
<div class="section" id="id10">
<h4>git-flow のインストール</h4>
<ol class="arabic">
<li><p class="first">github から git-flow をローカルにクローンしてください</p>
<blockquote>
<pre class="literal-block">
C:\&gt;git clone git://github.com/nvie/gitflow.git
remote: Counting objects: 2199, done.
remote: Compressing objects: 100% (885/885), done.
remote: Total 2199 (delta 1302), reused 2109 (delta 1225)
Receiving objects: 100% (2199/2199), 457.02 KiB | 231 KiB/s, done.
Resolving deltas: 100% (1302/1302), done.
</pre>
</blockquote>
</li>
<li><p class="first">同様にサブモジュールをクローンします</p>
<blockquote>
<pre class="literal-block">
C:\&gt;cd gitflow
C:\gitflow&gt;git submodule init
Submodule 'shFlags' (git://github.com/nvie/shFlags.git) registered for path 'shFlags'
C:\gitflow&gt;git submodule update
Cloning into shFlags...
remote: Counting objects: 454, done.
remote: Compressing objects: 100% (55/55), done.
remote: Total 454 (delta 389), reused 454 (delta 389)
Receiving objects: 100% (454/454), 101.30 KiB | 54 KiB/s, done.
Resolving deltas: 100% (389/389), done.
Submodule path 'shFlags': checked out '2fb06af13de884e9680f14a00c82e52a67c867f1'
</pre>
</blockquote>
</li>
<li><p class="first">git-flow のインストールスクリプトを実行します</p>
<blockquote>
<pre class="literal-block">
C:\gitflow&gt;contrib\msysgit-install.cmd
Installing gitflow into &quot;C:\Program Files\Git&quot;...
getopt.exe... Found
Copying files...
C:\tmp\gitflow\git-flow -&gt; C:\Program Files\Git\bin\git-flow
1 個のファイルをコピーしました
C:\tmp\gitflow\git-flow -&gt; C:\Program Files\Git\bin\git-flow
C:\tmp\gitflow\git-flow-feature -&gt; C:\Program Files\Git\bin\git-flow-feature
C:\tmp\gitflow\git-flow-hotfix -&gt; C:\Program Files\Git\bin\git-flow-hotfix
C:\tmp\gitflow\git-flow-init -&gt; C:\Program Files\Git\bin\git-flow-init
C:\tmp\gitflow\git-flow-release -&gt; C:\Program Files\Git\bin\git-flow-release
C:\tmp\gitflow\git-flow-support -&gt; C:\Program Files\Git\bin\git-flow-support
C:\tmp\gitflow\git-flow-version -&gt; C:\Program Files\Git\bin\git-flow-version
7 個のファイルをコピーしました
C:\tmp\gitflow\gitflow-common -&gt; C:\Program Files\Git\bin\gitflow-common
C:\tmp\gitflow\gitflow-shFlags -&gt; C:\Program Files\Git\bin\gitflow-shFlags
2 個のファイルをコピーしました
C:\tmp\gitflow\shFlags\src\shflags -&gt; C:\Program Files\Git\bin\gitflow-shFlags
1 個のファイルをコピーしました
</pre>
</blockquote>
</li>
</ol>
<p>※ 2011-10-26 現在、git-flow を Windows で実行するためには、
<tt class="docutils literal"><span class="pre">C:\Program</span> <span class="pre">Files\Git\bin\git-flow</span></tt> で <tt class="docutils literal">GITFLOW_DIR</tt> を <tt class="docutils literal">export</tt> する箇所をフルパスに書き換える必要があります。</p>
<blockquote>
<pre class="literal-block">
# export GITFLOW_DIR=$(dirname &quot;$0&quot;)
export GITFLOW_DIR=&quot;C:\\Program Files\\Git\\bin&quot;
</pre>
</blockquote>
<p>コマンドラインを再起動すると、git-flow が使えるようになっています。</p>
<blockquote>
<pre class="literal-block">
C:\gitflow&gt;git flow version
0.4.2-pre
</pre>
</blockquote>
</div>
</div>
</div>
</div>
<div class="section" id="id11">
<span id="usage"></span><h1>git-flow を利用する</h1>
<p>簡単な利用方法を例示するために、
<a class="reference external" href="https://github.com/">github</a> 上に spam というプロジェクトを作成します。</p>
<div class="section" id="origin">
<h2>中央 (<tt class="docutils literal">origin</tt>) を作成する</h2>
<p>git-flow プロジェクトの作成。</p>
<blockquote>
<pre class="literal-block">
$ mkdir spam
$ cd spam
$ git flow init
</pre>
</blockquote>
<p><tt class="docutils literal">git flow init</tt> を実行すると、対話式に以下の入力を求められます。</p>
<blockquote>
<pre class="literal-block">
Branch name for production releases: [master]
Branch name for &quot;next release&quot; development: [develop]

How to name your supporting branch prefixes?
Feature branches? [feature/]
Release branches? [release/]
Hotfix branches? [hotfix/]
Support branches? [support/]
Version tag prefix? []
</pre>
</blockquote>
<p>各ブランチを作成する際のブランチ名やそのプレフィックスを任意に指定できます。
今回はすべてデフォルトを利用します。</p>
<p><a class="reference external" href="https://github.com/">github</a> の Web サイトで spam というプロジェクトを作成し、コマンドラインから <tt class="docutils literal">origin</tt> として登録します。</p>
<blockquote>
<pre class="literal-block">
$ git remote add origin git&#64;github.com:[username]/spam.git
$ git push -u origin master
</pre>
</blockquote>
<p>すでに <tt class="docutils literal">origin</tt> が存在している場合は、</p>
<blockquote>
<pre class="literal-block">
$ git clone git&#64;github.com:[username]/[project].git
$ cd [project]
$ git flow init
</pre>
</blockquote>
<p>とすることにより、既存プロジェクトに git-flow を導入できます。</p>
</div>
<div class="section" id="id12">
<h2>機能を追加する</h2>
<p>spam プロジェクトに &quot;hello world&quot; と出力する機能を提供する <tt class="docutils literal">egg.py</tt> を追加してみます。</p>
<blockquote>
<pre class="literal-block">
git flow feature start hello
</pre>
</blockquote>
<p>これにより、 <tt class="docutils literal">feature/hello</tt> というブランチが作成され、スイッチされます。ブランチを確認してみましょう。</p>
<blockquote>
<pre class="literal-block">
$ git branch
  develop
* feature/hello
  master
</pre>
</blockquote>
<p>また、現在のフィーチャーブランチの一覧を確認してみましょう。</p>
<blockquote>
<pre class="literal-block">
$ git flow feature
* hello
</pre>
</blockquote>
<p>以下のような <tt class="docutils literal">egg.py</tt> を作成します。</p>
<blockquote>
<pre class="literal-block">
# -*- coding: utf-8 -*-
#!/usr/bin/env python

def hello():
    print &quot;hello world&quot;

if __name__ == '__main__':
    hello()
</pre>
</blockquote>
<p><tt class="docutils literal">egg.py</tt> をリポジトリに追加します。</p>
<blockquote>
<pre class="literal-block">
$ git add egg.py
$ git commit -m &quot;Add hello.&quot;
</pre>
</blockquote>
<p>この機能を開発用ブランチにマージします。</p>
<blockquote>
<pre class="literal-block">
$ git flow feature finish hello
</pre>
</blockquote>
<p>これにより、 <tt class="docutils literal">develop</tt> ブランチにマージされ、 <tt class="docutils literal">feature/hello</tt> ブランチは削除されます。</p>
<blockquote>
<pre class="literal-block">
$ git branch
* develop
  master
</pre>
</blockquote>
<p><tt class="docutils literal">develop</tt> ブランチを <tt class="docutils literal">origin</tt> に push します。</p>
<blockquote>
<pre class="literal-block">
$ git push origin develop
</pre>
</blockquote>
<p>github (origin) 上に <tt class="docutils literal">develop</tt> ブランチが作成され、 <tt class="docutils literal">egg.py</tt> が作成されているのが確認 (<a class="reference external" href="https://github.com/[username]/spam/tree/develop">https://github.com/[username]/spam/tree/develop</a>) できます。また、 <tt class="docutils literal">master</tt> ブランチには何も変更が加えられていないことも確認できます (<a class="reference external" href="https://github.com/[username]/spam">https://github.com/[username]/spam</a>)。</p>
</div>
<div class="section" id="id13">
<h2>リリースする</h2>
<p>リリース準備が整ったと仮定し、リリースブランチを作成します。</p>
<blockquote>
<pre class="literal-block">
$ git flow release start 1.0.0
</pre>
</blockquote>
<p>ブランチの状態を確認します。</p>
<blockquote>
<pre class="literal-block">
$ git branch
  develop
  master
* release/1.0.0
</pre>
</blockquote>
<p>リリース 1.0.0 のリリース準備が整いました。リリースに含まれる機能などを明記する <tt class="docutils literal">README.md</tt> を作成してみましょう。</p>
<blockquote>
<pre class="literal-block">
HISTORY
=======

- 1.0.0: Echo &quot;hello world&quot;
</pre>
</blockquote>
<p>リリース準備が終わったので、 1.0.0 をリリースしてみます。</p>
<blockquote>
<pre class="literal-block">
$ git add README.md
$ git commit -m &quot;Update README for release 1.0.0.&quot;
$ git flow release finish 1.0.0
</pre>
</blockquote>
<p>エディタが開きタグの入力を求められます。 <tt class="docutils literal">1.0.0</tt> 等を入力し、エディタを閉じます。ブランチやタグを確認してみましょう。</p>
<blockquote>
<pre class="literal-block">
$ git branch
* develop
  master

$ git tag
1.0.0
</pre>
</blockquote>
<p>各ブランチを <tt class="docutils literal">origin</tt> に push します。</p>
<blockquote>
<pre class="literal-block">
$ git push
</pre>
</blockquote>
<p>タグ 1.0.0 を <tt class="docutils literal">origin</tt> に push します。</p>
<blockquote>
<pre class="literal-block">
$ git push origin 1.0.0
</pre>
</blockquote>
<p>github (origin) 上の <tt class="docutils literal">master</tt> 、 <tt class="docutils literal">develop</tt> ブランチおよびタグ <tt class="docutils literal">1.0.0</tt> に <tt class="docutils literal">README.md</tt> が作成されているかを確認します。</p>
</div>
<div class="section" id="id14">
<h2>ホットフィックスを当てる</h2>
<p>ホットフィックス用のブランチを <tt class="docutils literal">master</tt> から作成します。</p>
<blockquote>
<pre class="literal-block">
$ git flow hotfix start 1.0.1-somefix
</pre>
</blockquote>
<p>ブランチの状態を確認します。</p>
<blockquote>
<pre class="literal-block">
$ git branch
  develop
* hotfix/somefix
  master
</pre>
</blockquote>
<p><tt class="docutils literal">egg.py</tt> をちょっとだけ編集します。</p>
<blockquote>
<pre class="literal-block">
$ git diff
diff --git a/egg.py b/egg.py
index 1340512..9125761 100644
--- a/egg.py
+++ b/egg.py
&#64;&#64; -2,7 +2,7 &#64;&#64;
 #!/usr/bin/env python

 def hello():
-    print &quot;hello world&quot;
+    print &quot;hello world!&quot;

 if __name__ == '__main__':
     hello()
</pre>
</blockquote>
<p>ホットフィックスを適用します。</p>
<blockquote>
<pre class="literal-block">
$ git commit egg.py -m &quot;Add missing char.&quot;
$ git flow hotfix finish 1.0.1-somefix
$ git branch
* develop
  master

$ git tag
1.0.0
1.0.1-somefix
</pre>
</blockquote>
<p><tt class="docutils literal">origin</tt> に push して確認してみましょう。</p>
<blockquote>
<pre class="literal-block">
$ git push
$ git push origin 1.0.1-somefix
</pre>
</blockquote>
<p>以上が、 git-flow を利用したブランチモデルの簡単な例となります。</p>
</div>
</div>
<div class="section" id="id15">
<span id="postscript"></span><h1>最後に</h1>
<p>今回は git-flow に触れてみました。同様のツールとして、ほかに <cite>Sotaro Karasawa</cite> 氏が公開されている <a class="reference external" href="https://github.com/sotarok/git-daily">git-daily</a> 等もあります。最初に戻りますが、git の長所の一つはブランチモデルです。自分やチームにあったツールを使い、 git のブランチモデルを最大限活用してみてください。</p>
</div>


]]>
    </content>
</entry>

<entry>
    <title>番外編: LiveCDのファイルシステム（2）</title>
    <link rel="alternate" type="text/html" href="http://www.oreilly.co.jp/community/blog/2011/02/filesystems-livecd-using-part2.html" />
    <id>tag:www.oreilly.co.jp,2011:/community/blog//4.737</id>

    <published>2011-02-28T01:00:00Z</published>
    <updated>2011-06-30T00:09:15Z</updated>

    <summary> 今回は、前回取り上げたsquashfs,aufs,cloop,dm-snapshotの各機能を用いた、LiveCDに必要な機能を実現するための組合せを考えてみます。 なお、本記事はhttp://so...</summary>
    <author>
        <name>千住治郎</name>
        
    </author>
    
        <category term="Programmer&apos;s High" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="ja" xml:base="http://www.oreilly.co.jp/community/blog/">
        <![CDATA[<div class="section" id="id7">
<p class="lead">今回は、前回取り上げた<tt class="docutils literal">squashfs</tt>,<tt class="docutils literal">aufs</tt>,<tt class="docutils literal">cloop</tt>,<tt class="docutils literal"><span class="pre">dm-snapshot</span></tt>の各機能を用いた、LiveCDに必要な機能を実現するための組合せを考えてみます。
なお、本記事は<a class="reference external" href="http://sourceforge.net/mailarchive/forum.php?thread_name=20986.1293537718%40jrobl&amp;forum_name=aufs-users">http://sourceforge.net/mailarchive/forum.php?thread_name=20986.1293537718%40jrobl&amp;forum_name=aufs-users</a>を元にしています。</p>
<h1>方式ごとの性能比較</h1>
<p>ここまで、<tt class="docutils literal">squashfs</tt>,<tt class="docutils literal">aufs</tt>,<tt class="docutils literal">cloop</tt>,<tt class="docutils literal"><span class="pre">dm-snapshot</span></tt>の各機能を簡単に紹介しました。次にLiveCDに必要な機能を実現するための組合せを考えてみます。組合せ要素には圧縮された読み取り専用下位レイヤ、書き込み可能上位レイヤ、両レイヤの結合方法の三種類がありますが、前述のようにdm-snapshotの場合にはファイルシステム種類に条件が加わるため、次のようになります（<a class="reference internal" href="#id8">表1</a>）。</p>
</div>
]]>
        <![CDATA[<div>
<p><span class="target" id="id8">表1</span> モジュール組合せ</p>
<table border="1" class="docutils">
<colgroup>
<col width="43%" />
<col width="30%" />
<col width="27%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head">下位レイヤ</th>
<th class="head">上位レイヤ</th>
<th class="head">スタック</th>
</tr>
</thead>
<tbody valign="top">
<tr><td>squashfs</td>
<td rowspan="2">tmpfs</td>
<td rowspan="2">aufs</td>
</tr>
<tr><td>任意のファイルシステム <tt class="docutils literal">+</tt> cloop</td>
</tr>
<tr><td colspan="2">writableな任意のファイルシステム
<tt class="docutils literal">+</tt> squashfs <tt class="docutils literal">+</tt> nested loopback</td>
<td>dm-snapshot</td>
</tr>
</tbody>
</table><br />
<p>必要な機能が揃えられたとして、次に性能を考えます。上記の複数あるLive CD実現法式を比較する場合、その内容は実質的に二種類に分かれます。すなわち、Union機能の比較と、読み取り専用圧縮レイヤのそれぞれを比較してみます。</p>
<div class="section" id="union">
<h2>Union機能の比較</h2>
<p>aufsとdm-snapshotの比較は、前述のようにcopy-upの際のコピー量が異なるため、サイズの大きなファイルの書き換えはdm-snapshotの方が良好な結果となることは容易に想像がつきます。同様に<tt class="docutils literal">readdir(3)</tt>もdm-snapshotの方が有利と予想できます。その他は上位から下位へとレイヤを辿る処理が実質的な差となると思われ、ブロックデバイスレベルとファイルシステムレベルの比較となり、これも全般的にdm-snapshotの方が有利と予想できます。ファイルシステムへのアクセスは内部でブロックデバイスへアクセスするわけですから、ファイルシステムレベルの方がオーバヘッドが大きくなることはまず間違いないでしょう。</p>
<div class="section" id="id9">
<h3>比較項目</h3>
<p>Union機能の比較には次のものを用います。Live CDではループバックマウントが用いられますが、ここでは簡略化のため用いないこととします。</p>
<blockquote>
<ol class="arabic simple">
<li>ext2</li>
<li>ext2(RO) + ext2(RW) + aufs</li>
<li>ext2 + dm-snapshot</li>
</ol>
</blockquote>
<p>比較方法は次の通りとし、所要時間を計測します。</p>
<blockquote>
<ol class="arabic simple">
<li><tt class="docutils literal">find(1)</tt></li>
<li>既存のファイルに1バイトを追加書きし、copy-upを発生させる。</li>
</ol>
</blockquote>
<p>いづれも小規模に実行しても、短時間すぎて比較しにくいので、充分に時間がかかるように複数回実行することにします。カーネルバージョンは2.6.33とします。ハードウェア環境はIntel Core2 Duo 3GHz、4GB RAMです。測定結果を<a class="reference internal" href="#id10">表2</a>に示します。測定スクリプト一式は添付ファイルに含めてありますので、必要に応じ参照してください（union.sh）。</p>
<p><span class="target" id="id10">表2</span>：Union機能の比較</p>
<table border="1" class="docutils">
<colgroup>
<col width="24%" />
<col width="11%" />
<col width="11%" />
<col width="16%" />
<col width="11%" />
<col width="11%" />
<col width="16%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head" rowspan="2">&nbsp;</th>
<th class="head" colspan="3">find</th>
<th class="head" colspan="3">copy-up</th>
</tr>
<tr><th class="head">usr</th>
<th class="head">sys</th>
<th class="head">elapsed</th>
<th class="head">usr</th>
<th class="head">sys</th>
<th class="head">elapsed</th>
</tr>
</thead>
<tbody valign="top">
<tr><td>ext2</td>
<td>0.89</td>
<td>1.32</td>
<td>3.02</td>
<td>0.00</td>
<td>0.03</td>
<td>0.36</td>
</tr>
<tr><td>AUFS</td>
<td>1.13</td>
<td>3.14</td>
<td>5.01</td>
<td>0.00</td>
<td>0.02</td>
<td>0.39</td>
</tr>
<tr><td>dm-snapshot</td>
<td>0.89</td>
<td>1.29</td>
<td>3.02</td>
<td>0.00</td>
<td>0.02</td>
<td>0.36</td>
</tr>
</tbody>
</table><br />
<p>ほぼ予想通りの結果ですが、今回の小規模な測定ではファイル数が少なく、またファイルサイズもそれほど大きくなかったためかcopy-upの方では有意な差が確認できませんでした。<tt class="docutils literal">find(1)</tt>の結果はやはりaufsが一番遅く、レイヤ数増加に伴い、時間がかかることが予想できます。 dm-snapshotでも僅かながら時間がかかっており、こちらもレイヤ数増加に伴い遅くなることが予想できますが、aufsよりは大きくないだろうと思われます。</p>
</div>
</div>
<div class="section" id="id11">
<h2>読み取り専用圧縮レイヤの比較</h2>
<div class="section" id="id12">
<h3>過去のレポート</h3>
<p>squashfsは歴史がある分、これまでにも性能比較レポートが何度か報告されています。比較的新しいものでは、 平成21年6月にWenqiang SongがLKMLに投稿した<a class="reference external" href="http://marc.info/?l=linux-kernel&amp;m=124447049111108&amp;w=2">"Squashfs 4.0 performance benchmark"</a>があります。</p>
<p>このレポートではsquashfsとext4をディレクトリルックアップ、シーケンシャルI/O、ランダム I/Oの三点で比較しています。大雑把にまとめると、</p>
<blockquote>
<ul class="simple">
<li>squashfsの速度は ext4と比較し遜色ない、またはより高速である。</li>
<li>squashfsは CPU(%sys)時間をより多く消費する。</li>
<li>squashfsはループバックマウントすると更に高速になる。</li>
<li>システムRAMサイズを制限すると遅くなり、ループバックマウントの場合でも差がなくなる。</li>
</ul>
</blockquote>
<p>また、このレポートで取り上げている比較項目は、更に古い squashfs 2.0、2.1時代のもの（<a class="reference external" href="http://www.celinuxforum.org/CelfPubWiki/SquashFsComparisons">"SquashFsComparisons"</a>。最終更新は 平成20年5月）と同等です。</p>
<p>こちらのレポートでは ext3、iso9660、zisofs（圧縮機能あり）、cloop、squashfs 2.0、2.1を比較しており、総じてsquashfs 2.1が優れている、つまり、より小さく、より高速という結果になっています。よく読むと、前述の<a class="reference external" href="http://marc.info/?l=linux-kernel&amp;m=124447049111108&amp;w=2">"Squashfs 4.0 performance benchmark"</a>と同じ点が目を引き、ext3と比較した場合にやはりsquashfsのCPU（%sys）が大きくなっています。</p>
</div>
<div class="section" id="lzma">
<h3>LZMA対応</h3>
<p><strong>正式な対応</strong> squashfsと cloopの圧縮方法には、従来から広く用いられている ZLIBと、比較的新しいLZMAがあります。 squashfsは linux-2.6.34でLZMA対応の実装を試み、一通りは動作しているようです<a class="footnote-reference" href="#id15" id="id13">[1]</a><a class="footnote-reference" href="#id16" id="id14">[2]</a>。</p>
<table class="docutils footnote" frame="void" id="id15" rules="none">
<colgroup><col class="label" /><col /></colgroup>
<tbody valign="top">
<tr><td class="label"><a class="fn-backref" href="#id13">[1]</a></td><td><a class="reference external" href="http://git.kernel.org/?p=linux/kernel/git/pkl/squashfs-lzma.git;a=summary">http://git.kernel.org/?p=linux/kernel/git/pkl/squashfs-lzma.git;a=summary</a></td></tr>
</tbody>
</table>
<table class="docutils footnote" frame="void" id="id16" rules="none">
<colgroup><col class="label" /><col /></colgroup>
<tbody valign="top">
<tr><td class="label"><a class="fn-backref" href="#id14">[2]</a></td><td>git://git.kernel.org/pub/scm/linux/kernel/git/pkl/squashfs-lzma.git</td></tr>
</tbody>
</table>
<p>しかし、 mainlineへの取り込みは見送られ、 2.6.35現在でも取り込まれていません。
LZMA対応は未だですが、LZO対応は2.6.36でmainlineへ取り込まれました。またXZ対応も進められています。</p>
<p><strong>sqlzmaパッチ</strong> squashfs開発者以外が行ったLZMA対応は複数知られており、その中でもsqlzmaパッチは広く用いられています<a class="footnote-reference" href="#id18" id="id17">[3]</a>。</p>
<table class="docutils footnote" frame="void" id="id18" rules="none">
<colgroup><col class="label" /><col /></colgroup>
<tbody valign="top">
<tr><td class="label"><a class="fn-backref" href="#id17">[3]</a></td><td><a class="reference external" href="http://www.squashfs-lzma.org">http://www.squashfs-lzma.org</a></td></tr>
</tbody>
</table>
<p>sqlzmaパッチは、別途開発された7-ZIP内のLZMAルーチンを採用しています。ユーザ空間のツール用に開発されたコードをカーネルモジュールにすることはそれほど珍しいことではありませんが、C++言語で書かれたLZMAルーチンをカーネルモジュールとして使用するのはかなりの力技です。前述のlinux-2.6.34でのLZMA対応でもやはり7-ZIP内のLZMAルーチンを使用していますが、こちらはC言語で書かれたルーチンを採用しており、そんな力技を用いていません。sqlzmaパッチが何故C++の方を採用したかと言うと、単に当時の7-ZIP内にC言語で書かれたルーチンがまだなかったというだけのことです。</p>
<p>また、sqlzmaパッチはハイブリッド方式を採用しています。一般に圧縮処理は、ランダムデータやバイナリファイルに対しては有効ではない場合があり、結果サイズが大きくなってしまうこともあります。ZLIB、LZMA複数の圧縮方式が同時に使用可能な環境では小さくなる方を採用したいところですが、どちらの方が小さくなるかは実際に圧縮してみなければ分かりません。このためsqlzmaパッチは、squashfsが前述のように指定サイズごとに圧縮することに基づき（デフォルトでは128KB）、この単位で両方の圧縮を試み、小さい方の結果を採用しています。まず圧縮処理の軽いZLIBで圧縮し、結果サイズが小さくなればLZMAで圧縮すれば更に小さくなることが期待できるため、同じ元データをLZMAでも圧縮し、結果サイズを比較し、小さい方を採用します。初めのZLIB圧縮でサイズが小さくならないデータはLZMAでもやはり小さくは出来ないだろうと見込み、圧縮しない元データを採用します。つまり、未圧縮、ZLIB圧縮、LZMA圧縮の三者からもっともサイズの小さなものを選択する方式です。もちろん、圧縮時の処理時間は長くなりますが、この労苦はLiveCD製作者のみが負担し、全ての利用者は通常負担しません。展開時には、squashfsが元来持っているブロック毎に圧縮されているか否かの情報と、圧縮データのヘッダからZLIB、LZMAを判断できるため、種別情報を追加する必要はありません。</p>
<p><strong>cloopの対応</strong> cloopも当初はZLIBによる圧縮だけでしたが、平成16年末頃のバージョンからは<cite>advancecomp</cite>という外部パッケージを用い、より圧縮率が高い（圧縮結果のサイズが小さくなる）LZMAも選択できるようになりました。しかし、v2.636ではまだユーザ空間（つまり<cite>create_compressed_fs</cite>コマンド）のみのようで、カーネルモジュールには実装されていません。</p>
<p><strong>カーネルの対応</strong> ファイルシステム作成時の圧縮処理はユーザ空間（コマンドレベル ）で実行されますが、マウント後の展開処理はカーネル空間です。つまりLinuxカーネル内に展開機能が必要です。ZLIB展開機能は古くから実装されていますが、LZMA展開は平成21年6月のlinux-2.6.30、LZO展開は平成22年2月のlinux-2.6.33で実装された比較的新しいものなので、ZLIB以外の圧縮を用いる場合にはカーネルのバージョンに注意が必要です。さらに、現在の利用方法は両者ともinitramfsの展開を主眼としており、ファイルシステムから利用するにはまだ実績不足の恐れもあります。</p>
<p>本記事では、ZLIB圧縮でのsquashfs、cloopの測定／比較は当然行うとして、他の圧縮方法を比較しないのはちと面白くありません。cloopのLZMAはひとまずあきらめるとして、squashfsについては2.6.34で見送られたLZMA対応のコードを開発者のGITリポジトリからローカルに取り込み、比較対象に加えます。更にsquashfsがmainlineへ取り込まれる以前に広く利用されたsquashfs 3.4用のsqlzmaパッチを比較してみます。こちらはlinux-2.6.27+squashfs 3.4ベースですので参考程度です。</p>
</div>
<div class="section" id="squahfs">
<h3>squahfs展開処理の並列化</h3>
<p>もともと squashfsの展開処理はシリアライズされています。例えば、同じ squashfs内の<tt class="docutils literal">fileA</tt>、<tt class="docutils literal">fileB</tt>に対し、それぞれ別のプロセスが同時にアクセスしても、先のファイル展開処理が完了するまで次のファイル展開には取り掛かりません。しかし、これでは昨今のマルチコアプロセッサのパワーを活用出来ないので、展開処理も並列化しようというパッチがLinux Kernel Mailing Listへ投稿されたことがあります<a class="footnote-reference" href="#id20" id="id19">[4]</a>。ZLIB展開の場合だけですが、これもローカルに取り込み、比較してみます。この機能はsqlzmaパッチでも実装されています。</p>
<table class="docutils footnote" frame="void" id="id20" rules="none">
<colgroup><col class="label" /><col /></colgroup>
<tbody valign="top">
<tr><td class="label"><a class="fn-backref" href="#id19">[4]</a></td><td><p class="first">次の3つ</p>
<ul class="last simple">
<li><a class="reference external" href="http://marc.info/?l=linux-fsdevel&amp;m=127884119303014&amp;w=2">[RFC0/2] squashfs parallel decompression</a></li>
<li><a class="reference external" href="http://marc.info/?l=linux-fsdevel&amp;m=127884119203011&amp;w=2">[RFC1/2] squashfs parallel decompression, early wait on buffer</a></li>
<li><a class="reference external" href="http://marc.info/?l=linux-fsdevel&amp;m=127884119303017&amp;w=2">[RFC2/2] squashfs parallel decompression, z stream per cpu</a></li>
</ul>
</td></tr>
</tbody>
</table>
</div>
<div class="section" id="id21">
<h3>比較項目</h3>
<p>上記を踏まえ、比較項目を以下のとおりとします。</p>
<blockquote>
<ol class="arabic simple">
<li>ext2</li>
<li>squashfs-4.0 GIP</li>
<li>squashfs-4.0 GIP -no-fragments</li>
<li>squashfs-4.0 GIP + parallel decompression patch</li>
<li>squashfs-4.0 GIP -no-fragments + parallel decompression patch</li>
<li>ext2 + cloop GZIP</li>
<li>ext2 + squashfs-4.0 GIP + nested loopback</li>
<li>squashfs-4.0 LZMA (linux-2.6.34-rc6)</li>
<li>squashfs-3.4 + sqlzma patch (linux-2.6.27.4)</li>
</ol>
</blockquote>
<p>squashfsのブロックサイズやLZMAの辞書サイズを調節すると所要時間を短縮できますが、バリエーションを増やすと測定の手間がかかりますし、条件が同一ならば傾向は変化しないだろうと思われるため、デフォルト値のみを採用することとします。</p>
<p>カーネルバージョンは、squashfs 4.0LZMAおよびsquashfs 3.4+sqlzmaを除き、すべて2.6.33とします。squashfs 4.0 LZMAは2.6.34-rc6、squashfs 3.4+sqlzmaは2.6.27.4とします。cloop-2.636はそのままではコンパイルできなかったので、2.6.33用に修正を加えました（cloop-2.636-2.6.33.patch）。</p>
<p>比較方法は次の通りとし、圧縮結果サイズと、読み取り／展開時の所要時間、CPU消費に注目します。 CPU時間の大半は別カーネルスレッドにより消費され、<tt class="docutils literal">find(1)</tt>や<tt class="docutils literal">cat(1)</tt>を対象とする<tt class="docutils literal">time(1)</tt>による単純測定では測定範囲外となるので、若干粗くなりますが、<tt class="docutils literal">vmstat(8)</tt>も併用することにします。カーネルレベルのプロファイラなどを使用すれば、より精度の高い測定が可能かもしれませんが、今回は粗いレベルで留めます。</p>
<blockquote>
<ol class="arabic simple">
<li>複数ファイルをreaddir(3)で見つかった順に<tt class="docutils literal">read(2)</tt>する（シーケンシャルアクセスとして）。</li>
<li>複数ファイルを<tt class="docutils literal">readdir(3)</tt>とは異なる順序で<tt class="docutils literal">read(2)</tt>する（ランダムアクセスとして）。</li>
</ol>
</blockquote>
<p>それぞれ、シングルプロセスで繰り返し<tt class="docutils literal">cat(1)</tt>する場合と、<tt class="docutils literal">cat(1)</tt>を複数同時に並列実行（マルチプロセス）する場合の二通りを測定します。 Union機能の比較と同様に複数回実行した測定結果を<a class="reference internal" href="#id22">図6</a>、<a class="reference internal" href="#id23">図7</a>に示します。測定スクリプト一式は添付ファイルに含めてあり（decomp.sh）、測定データは本記事末<a class="reference internal" href="#id24">表3</a>に掲載していますので、必要に応じて参照してください。</p>
<p><span class="target" id="id22">図6</span> 読み取り専用圧縮レイヤの比較（シングルプロセス、クリックすると拡大）</p>
<a href="./images/fig6.png"><img alt="./images/fig6.png" src="./images/fig6.png" width="550"/></a>
<p><span class="target" id="id23">図7</span> 読み取り専用圧縮レイヤの比較（マルチプロセス、クリックすると拡大）</p>
<a href="./images/fig7.png"><img alt="./images/fig7.png" src="./images/fig7.png" width="550" /></a>
<p><strong>ファイルサイズ</strong> 当然ですが、squashfsの<tt class="docutils literal"><span class="pre">-no-fragments</span></tt>オプションを用いない方がサイズは小さくなります。これは圧縮対象内に小さなサイズのファイル数に依存します。LZMAを用いると<tt class="docutils literal"><span class="pre">-no-fragments</span></tt>オプションを用いなくとも、更に小さくなっています（11.1%削減）。ハイブリッド圧縮方式を採用しているsqlzmaパッチでは更に小さくなります（11.2%削減）。</p>
<p>cloopはsquashfsよりも大きなサイズになっていますが、これは今回の測定方法にも一因があります。元のファイルシステムイメージをext2で作成しましたが、ピッタリのサイズにするのは手間がかかるため、イメージファイルはやや大きめにしてあり、未使用領域が含まれます（今回の測定に使用した元イメージのファイルサイズは約732MBで、その内の約75MBが未使用領域です）。この未使用領域が圧縮結果サイズにどれだけ影響するかは調べていませんが、恐らくは圧縮結果サイズを削減できる余地があると思われます。squashfsを作成する場合は未使用領域はゼロもしくは最少になります。ファイルシステム種別もiso9660などに変更することでサイズを削減できる期待があります。今回の測定では前述の通り特に圧縮時にパラメータは与えていませんが、もちろんブロックサイズを指定する方がずっと簡単なサイズ削減方法です。</p>
<p>cloopの元ファイルシステムイメージは ext2ですが、これを圧縮／squashfs化したものがフラグメントブロック不使用とキャッシュ効果を狙った<tt class="docutils literal">sq40nested</tt>です。同じ ZLIB圧縮なのに<tt class="docutils literal">sq40nested</tt>の方がcloopよりも圧縮結果サイズが小さくなっているのは、デフォルトブロックサイズの違いが主な要因と思われます。</p>
<p><strong>所要時間</strong> まず、<tt class="docutils literal">sq40</tt>（Squashfs4.0 in vanilla linux-2.6.33）に注目すると、シングルプロセスのシーケンシャルアクセス以外の遅さが目立ちます。マルチプロセスシーケンシャルアクセスでは二倍以上、ランダムアクセスでは四倍以上時間がかかっています。測定環境はdual coreで、理想的にはマルチプロセスはシングルプロセスの半分の所要時間になって欲しいところですが、マルチプロセスの場合でもあまり短縮されておらず、CPUを充分に使い切っていないことが分かります。ランダムアクセスが遅い理由は前述のフラグメントブロックにあり、マルチプロセスが遅い理由は並列展開に対応していないことにあると思われます。CPUコア数が一つのシステムでは、当然ですが、マルチプロセスや並列展開は決して性能向上に貢献しません。</p>
<p>フラグメントブロックを使用しない<tt class="docutils literal">sq40nofrag</tt>を見ると、マルチプロセスでの劣化が抑えられており、シングルプロセスと同等になっています。またランダムアクセスでもシーケンシャルアクセスの二倍程度の時間に抑えられています。（本当はdual coreなのですからマルチプロセスの場合はもっと改善されて良いはずですが）恐らく並列展開に対応していないことが原因だと思われます。</p>
<p>並列展開の効果については<tt class="docutils literal">sq40para</tt>、<tt class="docutils literal">sq40nofrag_para</tt>と比較すると分かります。それぞれlinux-2.6.33のsquashfsに前述のparallel decompression patchをあてたもので、用いたsquashfsイメージファイルは<tt class="docutils literal">sq40</tt>、<tt class="docutils literal">sq40nofrag</tt>と同じものです。シングルプロセスの所要時間は<tt class="docutils literal">sq40</tt>、<tt class="docutils literal">sq40nofrag</tt>と同等ですが、マルチプロセスの方は大きく改善されています。もちろんその分CPUを多く消費しています。</p>
<p><tt class="docutils literal">sq40nested</tt>は二重ループバックによるキャッシュが威力を発揮し、非圧縮の<tt class="docutils literal">ext2</tt>と同等の結果となりました。ただ、搭載RAMが少ない環境ではやはり劣化すると恐れがあります。</p>
<p><tt class="docutils literal">cloop</tt>はデフォルトの圧縮ブロックサイズがsquashfsよりも小さいためか、より長く時間がかかっています。マルチプロセスの場合に遅くなってしまうのは、やはり並列展開未対応が原因と思われます。しかし、ランダムアクセス時の劣化は少なくなっています。ブロックデバイスレイヤでの圧縮/展開という点が有利に働いたと思われます。</p>
<p><tt class="docutils literal">ext2</tt>では最初に測定したシングルプロセスシーケンシャルリードだけが時間がかかっていますが、これはディスクアクセスが影響したと思われ、実際CPUのio-waitが大きくなっています。以降の測定は（ほぼ？）全てのファイルがキャッシュされたようで、io-waitが発生していません。</p>
<p><strong>参考：LZMA対応</strong> 参考程度ですが、<tt class="docutils literal">sq40lzma</tt>（linux-2.6.34-rc6のsquashfs4.0 LZMA対応）と<tt class="docutils literal">sqlzma34</tt>（2.6.27.4 squashfs3.4用の sqlzmaパッチ）も同様に測定してみました。やはりLZMA展開処理が原因と思われる遅さが目立ち、<tt class="docutils literal">sq40</tt>（2.6.33の squashfs4.0 ZLIB）の二倍以上遅い結果となりました。<tt class="docutils literal">sqlzma32</tt>はハイブリッド圧縮、並列展開を実装しており、CPUをほぼ使い切っても、それでも遅い結果です。sq40lzmaは更に悪い。</p>
<p>しかし、 sq40lzmaは正式リリースされたものではありませんので、この簡単な測定 /比較だけをもってして遅いと決めつけるのはフェアではありません。今回は測定対象とはしませんでしたが、squashfsはLZMA以外の圧縮方法にも対応を進めており、mainlineにも取り込まれています。</p>
<p>本記事では既に広く利用されている squashfsを中心に、LiveCDの複数の実現法法を見直してみました。 cloopも dm-snapshotも、squashfsも aufsもどれも悪いものではありません。当然ながら、ユーザがサイズ、展開速度、メモリ消費などに明確に優先順位をつけ、最適な圧縮方法を決定するのが肝要となることは変りません。</p>
<p>（初出H22/12）</p>
<p>Copyright &copy; 2010-11 SENJU Jiro</p>
<p class="footnote">本記事のサンプルコードは、以下のリンクよりダウンロードすることができます。記事と合わせてご参照ください。<br />
[<a href="http://www.oreilly.co.jp/pub/pg_high/20110209.tar.bz2">サンプルコード</a>]<br />
また、本稿で取り上げた内容は、記事の執筆時の情報に基づいており、その後の実装の進展、変更等により、現在の情報とは異なる場合があります。<br />
本連載「プログラマーズ・ハイ」では読者からのご意見、ご感想を切望しています。今後取り上げるテーマなどについてもご意見をお寄せください。</p>

<p><span class="target" id="id24">表3</span>:読み取り専用圧縮レイヤの比較（図6および7の元データ）</p>
<table border="1" class="docutils">
<colgroup>
<col width="23%" />
<col width="12%" />
<col width="8%" />
<col width="11%" />
<col width="13%" />
<col width="7%" />
<col width="7%" />
<col width="8%" />
<col width="12%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head" rowspan="2">&nbsp;</th>
<th class="head" colspan="3" rowspan="2">size
(blocks,KB)</th>
<th class="head" rowspan="2">elaspsed
time</th>
<th class="head" colspan="4">CPU(dual core)</th>
</tr>
<tr><th class="head">usr</th>
<th class="head">sys</th>
<th class="head">idle</th>
<th class="head">io-wait</th>
</tr>
</thead>
<tbody valign="top">
<tr><td rowspan="4">ext2</td>
<td rowspan="4">-</td>
<td rowspan="2">seq</td>
<td>single</td>
<td>27.8</td>
<td>4</td>
<td>22</td>
<td>59</td>
<td>15</td>
</tr>
<tr><td>multi</td>
<td>2.2</td>
<td>10</td>
<td>57</td>
<td>33</td>
<td>0</td>
</tr>
<tr><td rowspan="2">rand</td>
<td>single</td>
<td>4.2</td>
<td>6</td>
<td>34</td>
<td>60</td>
<td>0</td>
</tr>
<tr><td>multi</td>
<td>2.4</td>
<td>10</td>
<td>57</td>
<td>33</td>
<td>0</td>
</tr>
<tr><td rowspan="4">sq40</td>
<td rowspan="4">437512,
218536</td>
<td rowspan="2">seq</td>
<td>single</td>
<td>9</td>
<td>3</td>
<td>41</td>
<td>53</td>
<td>3</td>
</tr>
<tr><td>multi</td>
<td>23.7</td>
<td>1</td>
<td>85</td>
<td>5</td>
<td>9</td>
</tr>
<tr><td rowspan="2">rand</td>
<td>single</td>
<td>44.6</td>
<td>1</td>
<td>48</td>
<td>51</td>
<td>0</td>
</tr>
<tr><td>multi</td>
<td>42.8</td>
<td>1</td>
<td>87</td>
<td>7</td>
<td>5</td>
</tr>
<tr><td rowspan="4">sq40para
(parallel
decompression)</td>
<td rowspan="4">同上</td>
<td rowspan="2">seq</td>
<td>single</td>
<td>8.9</td>
<td>3</td>
<td>42</td>
<td>55</td>
<td>0</td>
</tr>
<tr><td>multi</td>
<td>8</td>
<td>4</td>
<td>85</td>
<td>11</td>
<td>0</td>
</tr>
<tr><td rowspan="2">rand</td>
<td>single</td>
<td>44.6</td>
<td>1</td>
<td>49</td>
<td>50</td>
<td>0</td>
</tr>
<tr><td>multi</td>
<td>23.4</td>
<td>1</td>
<td>93</td>
<td>5</td>
<td>1</td>
</tr>
<tr><td rowspan="4">sq40nofrag</td>
<td rowspan="4">458264,
228904</td>
<td rowspan="2">seq</td>
<td>single</td>
<td>8.6</td>
<td>3</td>
<td>40</td>
<td>53</td>
<td>4</td>
</tr>
<tr><td>multi</td>
<td>8.7</td>
<td>4</td>
<td>77</td>
<td>11</td>
<td>8</td>
</tr>
<tr><td rowspan="2">rand</td>
<td>single</td>
<td>16.3</td>
<td>2</td>
<td>45</td>
<td>53</td>
<td>0</td>
</tr>
<tr><td>multi</td>
<td>13.4</td>
<td>3</td>
<td>85</td>
<td>9</td>
<td>4</td>
</tr>
<tr><td rowspan="4">sq40nofrag_para
(parallele
decompression)</td>
<td rowspan="4">同上</td>
<td rowspan="2">seq</td>
<td>single</td>
<td>8.6</td>
<td>3</td>
<td>41</td>
<td>55</td>
<td>1</td>
</tr>
<tr><td>multi</td>
<td>7.6</td>
<td>4</td>
<td>74</td>
<td>13</td>
<td>9</td>
</tr>
<tr><td rowspan="2">rand</td>
<td>single</td>
<td>16.4</td>
<td>2</td>
<td>44</td>
<td>53</td>
<td>1</td>
</tr>
<tr><td>multi</td>
<td>10.3</td>
<td>3</td>
<td>82</td>
<td>10</td>
<td>5</td>
</tr>
<tr><td rowspan="4">cloop</td>
<td rowspan="4">521632,
260552</td>
<td rowspan="2">seq</td>
<td>single</td>
<td>15.2</td>
<td>1</td>
<td>38</td>
<td>23</td>
<td>38</td>
</tr>
<tr><td>multi</td>
<td>22.7</td>
<td>1</td>
<td>37</td>
<td>7</td>
<td>54</td>
</tr>
<tr><td rowspan="2">rand</td>
<td>single</td>
<td>22.8</td>
<td>1</td>
<td>46</td>
<td>21</td>
<td>32</td>
</tr>
<tr><td>multi</td>
<td>25.7</td>
<td>2</td>
<td>52</td>
<td>4</td>
<td>42</td>
</tr>
<tr><td rowspan="4">sq40nested</td>
<td rowspan="4">515336,
257412</td>
<td rowspan="2">seq</td>
<td>single</td>
<td>11.7</td>
<td>2</td>
<td>36</td>
<td>29</td>
<td>33</td>
</tr>
<tr><td>multi</td>
<td>2.7</td>
<td>8</td>
<td>59</td>
<td>33</td>
<td>0</td>
</tr>
<tr><td rowspan="2">rand</td>
<td>single</td>
<td>5.1</td>
<td>5</td>
<td>36</td>
<td>56</td>
<td>3</td>
</tr>
<tr><td>multi</td>
<td>2.9</td>
<td>8</td>
<td>59</td>
<td>33</td>
<td>0</td>
</tr>
<tr><td rowspan="4">sq40lzma
(2.6.43-rc6)</td>
<td rowspan="4">391616,
195612</td>
<td rowspan="2">seq</td>
<td>single</td>
<td>29.7</td>
<td>1</td>
<td>47</td>
<td>51</td>
<td>1</td>
</tr>
<tr><td>multi</td>
<td>297.5</td>
<td>0</td>
<td>70</td>
<td>8</td>
<td>22</td>
</tr>
<tr><td rowspan="2">rand</td>
<td>single</td>
<td>280</td>
<td>0</td>
<td>50</td>
<td>50</td>
<td>0</td>
</tr>
<tr><td>multi</td>
<td>305.1</td>
<td>0</td>
<td>70</td>
<td>25</td>
<td>5</td>
</tr>
<tr><td rowspan="4">sqlzma34
(2.6.27.4)</td>
<td rowspan="4">390592,
195100</td>
<td rowspan="2">seq</td>
<td>single</td>
<td>21.1</td>
<td>1</td>
<td>47</td>
<td>52</td>
<td>0</td>
</tr>
<tr><td>multi</td>
<td>23.4</td>
<td>1</td>
<td>94</td>
<td>4</td>
<td>1</td>
</tr>
<tr><td rowspan="2">rand</td>
<td>single</td>
<td>184.8</td>
<td>0</td>
<td>50</td>
<td>50</td>
<td>0</td>
</tr>
<tr><td>multi</td>
<td>94.7</td>
<td>0</td>
<td>98</td>
<td>1</td>
<td>1</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
]]>
    </content>
</entry>

<entry>
    <title>番外編: LiveCDのファイルシステム（1）</title>
    <link rel="alternate" type="text/html" href="http://www.oreilly.co.jp/community/blog/2011/02/filesystems-livecd-using-part1.html" />
    <id>tag:www.oreilly.co.jp,2011:/community/blog//4.736</id>

    <published>2011-02-09T04:00:00Z</published>
    <updated>2011-06-30T00:09:34Z</updated>

    <summary> Linuxをハードディスクへインストールせずに使用するLiveCD/DVDも広く利用されるようになり、多くのディストリビューションが従来のインストール媒体／方法に加え、LiveCDをリリースしていま...</summary>
    <author>
        <name>千住治郎</name>
        
    </author>
    
        <category term="Programmer&apos;s High" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="ja" xml:base="http://www.oreilly.co.jp/community/blog/">
        <![CDATA[<img src="http://www.oreilly.co.jp/community/blog/images/union/pg_high_logo.png" alt="http://www.oreilly.co.jp/community/blog/images/union/pg_high_logo.png" />

<p class="lead">Linuxをハードディスクへインストールせずに使用するLiveCD/DVDも広く利用されるようになり、多くのディストリビューションが従来のインストール媒体／方法に加え、LiveCDをリリースしています。ほとんどのLiveCDではsquashfs、tmpfs、更にAUFSを用い、ハードディスクへインストールしない形態を実現していますが、本記事ではその他の方法も取り上げ、考察します。
なお、本記事は<a class="reference external" href="http://sourceforge.net/mailarchive/forum.php?thread_name=20986.1293537718%40jrobl&amp;forum_name=aufs-users">http://sourceforge.net/mailarchive/forum.php?thread_name=20986.1293537718%40jrobl&amp;forum_name=aufs-users</a>を元にしています。</p>
<div class="section" id="id1">
<h1>古代の構成</h1>
<p>一般的にLinux LiveCDではシステムをインストールした状態のファイルシステムを圧縮し、読み取り専用ファイルシステムイメージとして収納してあります。この際に用いられるのがsquashfsなどのファイルシステムですが、ここからそのままシステムを起動しても読み取り専用ですから、使用できない部分が生まれます。</p>
<p class="footnote">圧縮の反対動作を指す言葉は伸長とも表現されますが、本記事では展開と表現します。</p>
<p>例えば、システムを起動すれば各種ログファイルや<tt class="docutils literal">/var/wtmp</tt>ファイルに対する書き込みが発生しますし、ファイルシステムをマウントすれば<tt class="docutils literal">/etc/mtab</tt>というファイルも更新されます。デーモンが起動されればサービスに必要な各種ファイルなども作成されます。それぞれを一つづつ調査し、書き込みが発生しないように対処して行くことも不可能ではないかもしれませんが、もちろん手間がかかります。そもそもLinux（UNIX）システムは書き込み可能ファイルシステム上で動作することを前提としているのですから、この努力は有意義とは言えないでしょう。</p>

<p>かつてはシャドウディレクトリ方式で対応しようという時代もありました。シャドウディレクトリとはやや古い呼び方かもしれませんが、X.Org/XFree86以前のX11 Window Systemのビルドなどにも用いられていた（と思うけど、記憶に自信がなくなってきた ）形態です。例えば、ソースファイルが置かれた<tt class="docutils literal">src/</tt>下でそのままビルドすると、他のアーキテクチャ用にビルドする際には全オブジェクトファイルを削除しなければなりません。そうすると、先のアーキテクチャ用のリビルド時にはまた初めからすべてをコンパイルしなければならず、この手間暇を節約するための方策として、<tt class="docutils literal">obj/</tt>を別に用意し、全てのソースファイルのシンボリックリンクを<tt class="docutils literal">obj/</tt>下に作成する方法が採られました。この形態がシャドウディレクトリで、ビルドされるオブジェクトファイルは<tt class="docutils literal">obj/</tt>下に作成され、ソースファイルを書く場合にもディレクトリの差異を気にする必要もありません。<tt class="docutils literal">lndir(1)</tt>を用いると、子ディレクトリ、孫ディレクトリも含んだシャドウディレクトリを作成できます。</p>
</div>
]]>
        <![CDATA[<div>
<p>シャドウディレクトリの例</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">$ cd obj
$ lndir -silent ../src
$ cd ..
$ find src obj -ls
drwxr-xr-x ... src
-rw-r--r-- ... src/a.c
drwxr-xr-x ... src/sub
-rw-r--r-- ... src/sub/b.c
drwxr-xr-x ... obj
lrwxrwxrwx ... obj/a.c --&gt; ../src/a.c
drwxr-xr-x ... obj/sub
lrwxrwxrwx ... obj/sub/b.c --&gt; ../../src/sub/b.c
</pre></div>
<p>以前のLiveCDでは読み取り専用ファイルシステムイメージの作成時に、例えば<tt class="docutils literal">/readonly</tt>というディレクトリ下にシステム全体を格納し、<tt class="docutils literal">/etc</tt>、<tt class="docutils literal">/bin</tt>などを作成し、LiveCD起動時には<tt class="docutils literal">tmpfs</tt>をルートディレクトリとし、<tt class="docutils literal">/readonly/etc</tt>、<tt class="docutils literal">/readonly/bin</tt>下の全ファイルへのシンボリックリンクを作成するというシャドウディレクトリ方式が採られたことがあります。この状態ではファイルの新規作成は<tt class="docutils literal">tmpfs</tt>に対して行われ、読み取りにだけCD-ROM内のファイルシステムイメージが用いられ、当初の目的は達成できます。しかし、シンボリックリンク数は実用的ではない程膨大になり、システム起動にも時間がかかります。更に、既存のファイルを変更する場合には、ファイルを一度<tt class="docutils literal">tmpfs</tt>へコピーする手間もかかります。</p>
</div>
<div class="section" id="squashfsaufs">
<h2>squashfsとAUFS</h2>
<p>その後に用いられるようになったのがUnion機能を備えたファイルシステムである、AUFS、Unionfsです。この機能については以前の記事「<a class="reference external" href="http://www.oreilly.co.jp/community/blog/2010/02/union-mount-uniontype-fs-part-1.html">UnionMountとUnion-type Filesystem</a>」で取り上げました。簡単に振り返ると、CD-ROM内の読み取り専用ファイルシステムイメージをループバックマウントし、その上に書き込み可能なファイルシステムを透過的に重ねてマウントし、上述の読み取り／書き込みを実現します。既存ファイルを変更する際のコピーもUnion機能が備えるcopy-upにより自動的に行われます。レイヤは複数指定できるので、アプリケーション／パッケージの取捨選択にも応用できます。例えばSLAX LiveCDではコンパイラパッケージ、オフィススートなどを個別の読み取り専用ファイルシステムイメージに分割しており、ユーザが必要に応じレイヤとして追加します。</p>
<p>現在のLiveCDでは、上位の書き込み可能なレイヤにはtmpfsやHDD上のファイルシステムを指定し、下位の読み取り専用ファイルシステムには圧縮機能を備えたsquashfsを、Union機能にはAUFSをそれぞれ用いるのが一般的です。</p>
<p>squashfs作成例</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">$ mksquashfs /bin /dev /etc /lib /sbin /usr /tmp/squashfs.img \
   -no-progress -noappend -keep-as-directory -comp gzip
</pre></div>
<p>squashfsと aufsのマウント例 (LiveCD)</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">(initramfs内の起動スクリプト)
# mount -o ro /dev/cdrom /cdrom
# mount -o ro,loop /cdrom/squashfs.img /squashfs
# mount -t tmpfs none /tmpfs
# cd /tmpfs; mkdir -p tmp var/log; chmod 1777 tmp; cd /
# mount -t aufs -o br:/tmpfs:/squashfs none /aufs
(以降、/aufsをルートディレクトリしてシステム起動)
</pre></div>
<p>上の例を実行した結果構築されるファイルシステムとブロックデバイスのレイヤ関係を図示します（<a class="reference internal" href="#id2">図1</a>）。比較、参考のため、通常マウントのデバイス／レイヤ関係と、ループバックマウントの場合も図示します（<a class="reference internal" href="#id3">図2</a>、<a class="reference internal" href="#id4">図3</a>）。通常、ファイルシステムはブロックデバイスと一対一に対応しますが、ループバックマウントの場合は<tt class="docutils literal">/dev/loopN</tt>という特殊なブロックデバイスを用い、このデバイスが別途マウントされたファイルシステム内に存在する1ファイルに対応します（<a class="reference internal" href="#id4">図3</a>では<tt class="docutils literal">/dev/sda1</tt>内に存在する<tt class="docutils literal">squashfs.img</tt>）。</p>
<p><span class="target" id="id2">図1</span>:squashfsとaufsのマウント例（LiveCD）</p>
<img alt="./images/fig1.png" src="./images/fig1.png" />
<p><span class="target" id="id3">図2</span>：通常のファイルシステムマウント例</p>
<img alt="./images/fig2.png" src="./images/fig2.png" />
<p><span class="target" id="id4">図3</span>：ループバックマウント例</p>
<img alt="./images/fig3.png" src="./images/fig3.png" />
<p>しかし、上記のようなファイルシステム環境はこの組み合わせ以外でも実現可能です。本章では二つの方法を紹介します。</p>
</div>
<div class="section" id="cloop">
<h2>cloop：ループバックブロックデバイスの圧縮機能</h2>
<p>一つ目は圧縮機能をファイルシステムではなく下位に位置するブロックデバイスに実装したcloop（compressed loopback block device）というモジュールです。標準ではLinuxカーネルには含まれていません。</p>
<p><a class="reference external" href="http://debian-knoppix.alioth.debian.org/packages/cloop/cloop_2.636-1.tar.gz">http://debian-knoppix.alioth.debian.org/packages/cloop/cloop_2.636-1.tar.gz</a></p>
<p>ブロックデバイスレベルでの機能なのでファイルシステム種類を問いません。一度ファイルシステムイメージを作成し、必要ファイルをコピーした後に、<tt class="docutils literal">create_compressed_fs</tt>コマンド（別名<tt class="docutils literal">advfs</tt>）でファイルシステムイメージを圧縮し、cloopイメージを作成します。</p>
<p>cloopイメージの作成/マウント</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">$ dd if=/dev/zero of=/tmp/ext2.img bs=10M count=1   ---ファイル領域確保
$ mkfs -t ext2 -F -m0 -q -O dir_index /tmp/ext2.img ---ext2イメージ作成
$ sudo mount -o loop /tmp/ext2.img /mnt             ---/mntへマウント
$ tar -C /bin -cf - . | sudo tar -C /mnt -xpf -     ----/binを/mntへコピー
$ sudo umount /mnt
$ create_compressed_fs -q -L9 /tmp/ext2.img /tmp/ext2.cloop.img ---cloopイメージ作成
$ sudo losetup -r /dev/cloop0 /tmp/ext2.cloop.img   ---cloopデバイスと関連付け
$ mount -o ro /dev/cloop0 /mnt                      ---cloopデバイスをマウント（以降/mntをext2として使用可能)
</pre></div>
<p>レイヤ関係は基本的に<a class="reference internal" href="#id4">図3</a>と変らず、squashfs以外の任意のファイルシステムを圧縮、使用できます。ループバックブロックデバイスは<tt class="docutils literal">/dev/loopN</tt>から<tt class="docutils literal">/dev/cloopN</tt>へ変更します。上の例では<tt class="docutils literal">/dev/cloop0</tt>というデバイスを用いていますが、これはcloopモジュールがロード時に作成するものです。</p>
<p>また、cloopは書き込みには対応していませんので、cloopでループバックマウントしたファイルシステムは読み取り専用になるため、（疑似的に）書き込みを実現するには、AUFSなどスタッカブルファイルシステムを用いる必要があります。</p>
<p>cloopモジュールを採用しているLiveCDにはKnoppix（とその派生物）があります。</p>
</div>
<div class="section" id="dm-snapshot">
<h2>dm-snapshot：デバイスマッパのスナップショット機能</h2>
<p>もう一つの方法はスタッカブルファイルシステムの代わりにデバイスマッパのスナップショット機能を用いるものです。デバイスマッパは、やはりブロックデバイスレベルの機能で、論理ボリューム管理（LVM）などの上位ツールから利用されるのが一般的ですが、<tt class="docutils literal">dmsetup(8)</tt>コマンドを用い、デバイスマッパを直接操作することも可能です。</p>
<p>デバイスマッパはファイルシステムの下位に位置するブロックデバイスを操作するもので、スナップショット以外にも暗号化、リニア、ストライプ、ミラーなどの機能があります。暗号化機能はデバイスに対する書き込み/読み取りをデバイスマッパが符号化/複合化するもので、デバイス上には符号化された結果が書き込まれます。リニア／ストライプ／ミラーはRAID一般の機能なので、本記事では割愛します。</p>
<p>スナップショット機能は、デバイスマッパに二つのブロックデバイス、それぞれオリジナルデバイス、COWデバイスと呼びます、を指定し、書き込みはすべてCOWデバイスへ向け、読み取りは対象がすでにCOWデバイスに存在すればCOWデバイスから、存在しなければオリジナルデバイスから読み取ります。まさに、スタッカブルファイルシステム内の読み書き動作が上位/下位レイヤへ振り分けられる動作です（<a class="reference internal" href="#id5">図4</a>）。COWデバイスには変更差分が蓄積され、オリジナルデバイスは変化しません。</p>
<p class="footnote">デバイスマッパには zeroという機能（dm-zero）もあり、<tt class="docutils literal">/dev/zero</tt>に似た動作をするブロックデバイスを作成できます（<tt class="docutils literal">/dev/zero</tt>はキャラクタデバイスです）。すなわち、書き込みは何もせずに単に成功を、読み取りには常に0を返すデバイスです。筆者は有効性を確認していませんが、dm-zeroとスナップショットを組み合わせ、ブロックデバイスやファイルシステムのテストに応用する方法があります。実デバイスよりもサイズが大きな zeroデバイスを作成し、これをCOWデバイスとし、実デバイスのスナップショットを作成します。もし、実デバイスの範囲を越えた位置に I/Oが発生すると、そのI/OはCOWデバイスへ向けられ、結果的に正常に処理されず、エラーを検知できるという方法です。</p>
<p><span class="target" id="id5">図4</span>：dm-snapshotマウント例</p>
<img alt="./images/fig4.png" src="./images/fig4.png" />
<p>デバイスマッパによるブロック単位の Copy-on-Write（COW）</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">（16MBのext2イメージを作成）
$ dd if=/dev/zero of=/tmp/ext2.img bs=1M count=16
$ mkfs -t ext2 -F -q /tmp/ext2.img
$ sudo mount -o loop /tmp/ext2.img /mnt
$ &gt; /mnt/fileA
$ sudo umount /mnt

（サイズが1MBのCOWイメージを作成、sparseファイル）
$ dd if=/dev/zero of=/tmp/cow.img bs=1 count=1 seek=$((1024 * 1024 -1))

（ファイルとループバックデバイスを結合）
$ sudo losetup -r /dev/loop0 /tmp/ext2.img
$ sudo losetup /dev/loop1 /tmp/cow.img

（二つのデバイスを結合）
$ echo 0 $(du /tmp/ext2.img | cut -f1) snapshot /dev/loop0 /dev/loop1 p 0 | sudo dmsetup create dm-cow
（結合したデバイスをマウント）
$ sudo mount /dev/mapper/dm-cow /mnt
（以降/mntへの書き込みは/tmp/cow.imgへ蓄えられ、/tmp/ext2.imgは変更されない）
</pre></div>
<p>動作がスタッカブルファイルシステムとほぼ同じだとしても、操作対象が異なります。すなわち、デバイスマッパではブロックデバイス／セクタを対象とするのに対し、スタッカブルファイルシステムではファイルシステム／ファイルを対象とします。例えば、ブロックサイズを512バイトとし、サイズが2KBのファイルがオリジナルデバイスにあったとします。つまりファイルシステム上の4ブロックを消費しているファイルです。このファイルの先頭から1KB目の1バイトだけを書き換えると（3ブロック目の先頭バイト）、デバイスマッパのスナップショット機能は、3ブロック目だけをCOWデバイスへ書き込みます。変更されない他のブロックは書き込まれません（厳密に言えば、ファイル更新時刻などinodeが持つ情報も更新されるため、inodeブロックも書き込まれます）。言い換えるとオリジナルデバイスはファイルシステムが構築されマウント可能な状態であっても、COWデバイスにはファイルシステムが構築されずファイルシステム内の一部のブロックだけが保存されており、それだけをマウントすることができません。一方、スタッカブルファイルシステムではファイル単位で操作をするので、下位レイヤからファイル全体（2KB）を上位へコピーし、その後上位レイヤで本来要求された書き換えを処理します。</p>
<p>実行効率を考えれば、ファイル全体をコピーしないデバイスマッパの方が有利ですが、変更差分だけを確認したいといった場合の利便性では不利となる場合も考えられます。スタッカブルファイルシステムでは上位レイヤに対し、<tt class="docutils literal">ls(1)</tt>すれば変更されたファイル一覧が得られますし、下位レイヤと<tt class="docutils literal">diff(1)</tt>することもできます。この機能は差分バックアップにも応用できます。デバイスマッパではCOWデバイスだけをマウントすることはできず、当然<tt class="docutils literal">ls(1)</tt>もできません。COWデバイスは常にオリジナルデバイスと組み合わせて使用する必要があります。また、COWデバイスと結合していない時などに、オリジナルデバイスに直接変更を加えてしまうと、整合が取れなくなり、それまでのCOWデバイスが使えなくなる恐れもあります。</p>
<p class="footnote">COWデバイスの内容をオリジナルデバイスへ書き戻す、スナップショットマージ機能もありますが、本記事では割愛します。</p>
<p>デバイスマッパは既存のデバイスから新たにデバイスを作成しますが、この新デバイスに対してもデバイスマッパは動作します。この特徴を活かし、スナップショットを複数重ねることも可能です。スタッカブルファイルシステム風に言うと、レイヤ（ブランチとも呼ぶ）を複数使用できます。</p>
<p>下位レイヤは読み取り専用デバイスですが、デバイス内に構築するファイルシステムには書き込み機能が必要となる点には注意が必要です。ファイルを書き込むのはファイルシステムの機能なので、オリジナルデバイスに構築するのがsquashfsのような読み取り専用ファイルシステムだと、COWデバイスを追加してもマウントするのはsquashfsとなり、書き込むことができません。しかし、圧縮機能は是非とも欲しいところです。Linuxシステムを一般的にインストールしたファイルシステムを圧縮せずにLiveCDに収めるのは随分窮屈になってしまいます。デバイスマッパを採用しているLiveCDにはFedoraがありますが、Fedora(少なくともバージョン12)ではインストールした状態をext3（名前と異なり中身はext4です）イメージとして作成し、このファイル一つだけを含むsquashfsを作成しています。この場合、マウント時に二重にループバックマウントします。若干簡略化した例をレイヤ図（<a class="reference internal" href="#id6">図5</a>）と共に示します。</p>
<p>デバイスマッパとsquashfsを併用した例</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">$ sudo mount -o ro /dev/cdrom /cdrom
$ sudo mount -o ro,loop /cdrom/squashfs.img /squashfs
$ sudo losetup -r /dev/loop1 /squashfs/ext3.img
$ dd if=/dev/zero of=/tmp/cow.img bs=1 count=1 seek=$((1024 * 1024 -1))
$ sudo losetup /dev/loop2 /tmp/cow.img
$ echo 0 $(du /tmp/ext3.img | cut -f1) snapshot /dev/loop1 /dev/loop2 p 0 | sudo dmsetup create dm-cow
$ sudo mount /dev/mapper/dm-cow /mnt
</pre></div>
<p>少し面倒ですが、この例は文章でも補足しておきます。<a class="reference internal" href="#id6">図5</a>を下から上へ追ってみます。 cd-romをマウントすると、<tt class="docutils literal">squashfs.img</tt>というファイルがあり、これをループバックマウントします。するとそこには<tt class="docutils literal">ext3.img</tt>というファイルだけがあり、これを<tt class="docutils literal">/dev/loop1</tt>と結合し、ブロックデバイスとして扱えるようにします。別途作成した空ファイルも<tt class="docutils literal">/dev/loop2</tt>と連結し、<tt class="docutils literal">loop1</tt>をオリジナルデバイス、<tt class="docutils literal">loop2</tt>をCOWデバイスとして、新デバイス<tt class="docutils literal"><span class="pre">dm-cow</span></tt>を作成し、これをマウントすると（やっと）読み書き可能なファイルシステムとして利用可能になります。</p>
<p><span class="target" id="id6">図5</span>：デバイスマッパと squashfsを併用した例</p>
<img alt="./images/fig5.png" src="./images/fig5.png" />
</div>
<div class="section" id="squashfs">
<h2>二重ループバックマウントとsquashfsのキャッシュ</h2>
<p>二重ループバックマウントのメリット、デメリットを考えてみましょう。</p>
<p>まず資源消費が思い当たります。ブロックデバイスが増えると言うことは、そのデバイス用にキャッシュすることになり、これは不必要な二重キャッシュの恐れがあります。システム内部ではマウント毎にオブジェクトを作成し、内部のマウントツリーに追加します。すなわち、メモリを消費し、アクセス時にはツリーを辿る処理が増えます。また、ループバックブロックデバイスも一つ余計に消費します。一般的にデバイスにはその種類毎に0から番号が振られ、この番号には上限があります。ループバックブロックデバイスの場合、デフォルトでは0-7まで八つのデバイスが作成され、モジュールパラメータを明示的に指定すれば255まで拡張可能ですが、二重ループバックマウントの場合は、大雑把に言って二倍の消費量となります。更に<tt class="docutils literal">loopN</tt>という名前のカーネルスレッドがループバックマウント毎に起動され、常駐します。これもメモリ消費、プロセス ID空間消費、スケジューラの処理増加につながります。</p>
<p>デメリットを先に挙げましたが、悪いことばかりではありません。もっとも大きな効果は、圧縮されたsquashfsを展開した結果をキャッシュする点です。<tt class="docutils literal">mksquashfs</tt>は圧縮効率を高める（結果サイズをより小さくする）ため、指定されたサイズごと（squashfsのブロックサイズ。デフォルトでは128KB）に圧縮しますが、圧縮対象ファイルサイズがこのブロックサイズよりも小さい場合は、他のサイズが小さいファイルとまとめて圧縮します。これをフラグメントブロックと言います。このため、サイズが1KBのファイル<tt class="docutils literal">fileA</tt>と同じく1KBの<tt class="docutils literal">fileB</tt>が存在する場合、<tt class="docutils literal">fileA</tt>にアクセスしても、squashfs内部動作としては<tt class="docutils literal">fileB</tt>も同時に読み取り/展開することになります。展開結果はキャッシュされますが、フラグメントブロックについては一般に使用されるキャッシュ機構（ページキャッシュ）を用いず、squashfsが独自に実装したキャッシュ機構（フラグメントキャッシュ）を用います。この点についてはページキャッシュを利用した方が良いと言う指摘が度々あるようですが、現在までsquashfsは独自路線を維持しています。</p>
<p class="footnote">このフラグメントキャッシュ量はconfig時に変更可能です。<tt class="docutils literal">CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE</tt>がそれで、デフォルト値は3です。</p>
<p>展開された<tt class="docutils literal">fileA</tt>については一般的なファイル読み取り処理の一環としてページキャッシュに蓄えられますが、同じフラグメントブロックに存在する他のファイルについてはページキャッシュにはキャッシュされず、squashfs独自のフラグメントキャッシュにのみ残されます。次に<tt class="docutils literal">fileB</tt>へアクセスし、ファイルデータが<tt class="docutils literal">fileA</tt>の場合と同じフラグメントブロックに存在する場合、フラグメントキャッシュに残っていれば良いのですが、残っていなければ再び読み取り、展開します。フラグメントキャッシュのデフォルト値は大きくありませんから、アクセスパターンがランダムの場合にはキャッシュから追い出されている場合が多くなり、結果的に実行速度が犠牲になってしまいます。フラグメントキャッシュサイズを大きくすれば良いと言えばその通りですが、このキャッシュは squashfs内で固定的に確保され、ページキャッシュの様に動的に増減はしないので、メモリ圧迫の恐れがあります。</p>
<p>しかし、二重ループバックマウントすると、二番目のループバックブロックデバイスがフラグメント部分を含め展開結果を全てキャッシュ対象（ページキャッシュ）とするため、同じブロックを展開する機会が減り、アクセス速度は犠牲になりにくくなります。もちろん、不必要に二重、三重にキャッシュする恐れはあります。しかし、キャッシュによる速度改善は大きなものですし、またページキャッシュは参照頻度の低いものから順に破棄されるため、二重ループバック環境では下位キャッシュ（squashfs）から先に破棄され、上位キャッシュ（上例のext3）は生き残りやすい傾向が予想できます。このため前述のデメリットを補って余りある速度性能向上が期待できます。また、squashfs内に含まれるファイルがサイズの大きなファイルシステムイメージ一つとなるため、フラグメントブロックは実質的に発生しません。</p>
<p class="footnote"><tt class="docutils literal">mksquashfs</tt>にはフラグメントブロックを使用しない<tt class="docutils literal"><span class="pre">-no-fragments</span></tt>オプションが用意されており、圧縮サイズは犠牲になりますが、アクセスパターンがランダムの場合には効果を発揮します。</p>
</div>
<div class="section" id="cpu">
<h2>ブロックデバイス読み取り速度と CPU展開速度</h2>
<p>一般にCPU動作はブロックデバイス動作よりも高速で、また展開処理はCPUを多く消費します。圧縮されたファイルまたはファイルシステムを読み取る際にはこの点が重要となる場合があります。</p>
<p>一般的なデスクトップPC環境では、圧縮しないファイルを読み取る場合の方が、展開処理に時間がかかるため、圧縮した場合よりも高速となる場合が多いのですが、低速なブロックデバイスを使用する環境ではCPU/ブロックデバイスの速度差が開き、圧縮した場合の方が動作が高速になるという逆転が発生する場合があります。例えば、サイズが4KBのファイルを圧縮して1KBになった場合、低速なブロックデバイスから4KBを読み取るよりも、1KBを読み取り、展開した方が短時間で済む場合があります。つまり、展開処理に時間をかけることになっても、ブロックデバイスからの読み取り時間短縮の方が効果が大きいということです。</p>
<p>（初出H22/12）</p>
</div>
<div>
<p>Copyright &copy; 2010-2011 SENJU Jiro</p>
<p class="footnote">本記事のサンプルコードは、以下のリンクよりダウンロードすることができます。記事と合わせてご参照ください。<br />
[<a href="/pub/pg_high/ph20110209.tar.bz2">サンプルコード</a>]<br />
また、本稿で取り上げた内容は、記事の執筆時の情報に基づいており、その後の実装の進展、変更等により、現在の情報とは異なる場合があります。
本連載「プログラマーズ・ハイ」では読者からのご意見、ご感想を切望しています。今後取り上げるテーマなどについてもご意見をお寄せください。</p>
</div>]]>
    </content>
</entry>

<entry>
    <title>その1：ログ処理編-CDBを利用した簡易KVS</title>
    <link rel="alternate" type="text/html" href="http://www.oreilly.co.jp/community/blog/2011/01/processing-log-with-python.html" />
    <id>tag:www.oreilly.co.jp,2011:/community/blog//4.727</id>

    <published>2011-01-18T01:00:00Z</published>
    <updated>2011-01-18T01:22:45Z</updated>

    <summary> 大量のリクエストを捌くWebサイトを構築するには、さまざまなノウハウがあります。例えばオライリーの書籍『ハイパフォーマンスWebサイト』では、クライアントに配信するコンテンツを最適化することでパフォ...</summary>
    <author>
        <name>Ransui Iso</name>
        
    </author>
    
        <category term="1ヶ月10億超のリクエストを処理するPythonベースの広告配信システムの中身をちょっとだけ" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="ja" xml:base="http://www.oreilly.co.jp/community/blog/">
        <![CDATA[<div class="section" id="id1">
<p class="lead">大量のリクエストを捌くWebサイトを構築するには、さまざまなノウハウがあります。例えばオライリーの書籍『ハイパフォーマンスWebサイト』では、クライアントに配信するコンテンツを最適化することでパフォーマンスの向上を図っています。今回は、Pythonを使って月間10億を超える（！）リクエストを捌いている、株式会社クロスリスティングの方に、そのノウハウの一部を寄稿していただきました。<br />また、本記事の内容は<a href="http://atnd.org/events/2906">Python Hack-a-thon 2010.07</a>でのプレゼンテーションを元にしています</p>
</div>
<div class="section" id="id2">
<h2>自己紹介</h2>
<p>現在、私はクロスリスティングという会社で、広告配信システムとか、その周辺の新規商品関連の研究開発をやっています。メインのプログラミング言語はPythonです。自社でのシステム開発は基本的に全てPythonを使っています。自分は研究開発的なポジションですが、技術チームが開発しているプロダクションシステムもほぼ100% Pythonで開発されています。</p>
<p>「Pythonはバイトコードインタプリタのランタイムで動作するので、（JITを含めて）ネイティブコードにコンパイルする言語に比べて、実効速度の点で不利でありハイトラフィックなWebサービスの実装言語として向かないのではないか」という話を良く聞きます。今回は、Pythonで構築されているWeb広告配信サーバと、その周辺ツールについて、すこし紹介できればと思っています。</p>
</div>]]>
        <![CDATA[<div class="section" id="id3">
<h2>広告配信システムとログ処理</h2>
<p>私がいま務めているクロスリスティングという会社は、検索連動型広告というネット広告の配信をしています。検索連動型広告は「リスティング広告」とも呼ばれていて、会社の名前はそこから取っています。</p>
<p>「検索連動型広告」という言葉を聞きなれない方もいるかと思いますが、例をあげるとGoogleのAdWords広告とか、Yahooスポンサードサーチが有名です。</p>
<p>Webを検索すると、検索結果の上の部分にいくつかの広告リンクが表示されます。あの部分がリスティング広告枠と呼ばれているもので、ユーザの入力したキーワードにマッチしたものを、広告配信システムが選択して表示しているわけです。</p>
<img alt="./images/data-w-cdb01.png" src="./images/data-w-cdb01.png" />
<p>検索連動型広告は、多くの場合クリック課金です。つまり検索したユーザが広告をクリックすると、その広告ごとに設定された料金が広告主に課金されます。料金は、広告主が自分で設定しますが、同じ検索ワードに対して複数の広告主が存在した場合、設定された料金や、クリック率や、その他の検索結果との親和性とか、その他の色々な指標を用いて生成したスコア値を使って評価して、より高評価のものが選択され表示されます。</p>
<p>広告主は、広告効果を最大化したいので「どんな検索ワードに広告を出すと効果的か」「クリック単価はどの程度にすべきか」等を検討するために、入稿した広告ごとの詳細なレポートが必要です。また、広告スペースを提供しているメディア（ポータルサイト等）は、自社の検索サービスによって得られる収益がどのようになっているかを確認する必要があります。また、私のいる会社のような配信事業者は、「どのようなユーザに対して、どのような基準で広告を選択するのがもっとも良いか」といった問題に取り組むための分析用データが必要です。</p>
<p>これらのデータは全てログデータから生成されます。我々の会社はGoogleやYahooと比較して小さい会社ですが、検索連動型広告分野では国内で3番手にいて、それなりのリクエスト量があります。だいたい1ヶ月で10億超のリクエストが来て、どのメディアでどんな検索がされたか、どの広告がユーザに提示されたか、そのうちのどれがクリックされたか、クリックされた時の状況などが全て記録されます。このくらいの量になってくると、集計や分析のために、ログデータをRDBMSに入れてSQLベースで処理するのも、なかなか面倒なレベルになってくるので、ちょっとした工夫をしています。</p>
</div>
<div class="section" id="id4">
<h2>鴨射ちにバズーカ砲はいらない</h2>
<p>Google、Yahoo、mixi、Facebook等の超巨大サイトの場合、ユーザの行動履歴データは膨大な量になるでしょうが、我々のシステムでは、検索が1日あたり数千万件単位、表示される広告の数は検索のおよそ数倍、クリックは数十万件単位、識別しているユーザ数は、だいたい一千万弱という規模です。</p>
<p>このくらいの量のデータは、ちょっとした検索や集計を行いたい場合に、最近はやりのHadoopとかHiveといった分散系システムを持ち出して、ノードマシンに投資するには微妙なラインです。また日々増えていくデータをアーカイブしながら、それをそのまま利用したいという要望もあるので、RDBMSで全部やるというのも無理がありそうという、なんとも悩ましい規模感なわけです。</p>
<p>あまり大きな会社ではないので、サーバマシンへの投資は節約したいですし、「戦車にバズーカ砲で挑む」というような規模でもないので、小回りが効いて使い勝手のいい「鴨射ち散弾銃」をこしらえて問題を解決しています。</p>
<p>ログデータというのは、一度生成されると以後変化が無いという性質があります。お客さんへの請求や、メディアサイトへの収益分配の基本となるデータなので、変更があっては困ります。日常的に行うちょっとした分析や、状況の確認等に使っている「鴨射ち散弾銃」は、このログデータの静的な性質を利用して、コンパクトに仕立てています。逆に言うと、随時更新される動的な対象には全くもって向いていないということでもあります。</p>
</div>
<div class="section" id="cdb">
<h2>CDBを使う</h2>
<p><a class="reference external" href="http://cr.yp.to/cdb.html">CDB</a>をご存知ですか？ CDBはConstant DataBaseの略語で、qmailや djbdnsで有名なDaniel J. Bernstein氏の開発した、Unix系OSでは昔からよく使われているDBM系と言われるデータベースです。Key - Valueの単純なマップを提供するDBで、基本的なコンセプトは最近流行のKVSと同じです。ただしサーバがいて、ネットワーク越しにアクセスするものではなく、プログラムにライブラリとして組み込んで、ローカルファイルをストレージとしてデータの入出力を行います。ちょうどクライアントサーバ型のRDBMSとSQLiteのような関係です。<a class="reference external" href="http://www.gnu.org/software/gdbm/">GDBM</a>や<a class="reference external" href="http://www.oracle.com/us/products/database/berkeley-db/index.html">Berkeley DB</a>などの実装が有名で良く使われており、<a class="reference external" href="http://fallabs.com/tokyocabinet/">TokyoCabinet</a>など日本発の高速＆高機能なシステムもあります。</p>
<p>そのような中でCDBを選択したのにはちゃんとした理由があります。</p>
<ul class="simple">
<li>CDBはその名のとおりConstantなDBです。<ul>
<li>CDBは作成フェーズと参照フェーズに分かれています。</li>
<li>作成が完了した後に、DB内に格納されているエントリの修正、追加、削除はできません。</li>
<li>そのかわり作成、参照は超速いです。</li>
</ul>
</li>
<li>生成されるストレージファイルが比較的コンパクトです。</li>
<li>1つのキーに対して複数の値を設定可能です。</li>
</ul>
<p>という特徴が、静的なログ処理には向いています。作成フェーズと参照フェーズに分かれているという部分は実行例を見るのがよいでしょう。ここでは CDB-0.75、Python 2.6.5、 python-cdb-0.34（<a class="reference external" href="http://pilcrow.madison.wi.us/#pycdb">http://pilcrow.madison.wi.us/#pycdb</a>）をつかっています。</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"># -*- coding: utf-8 -*-</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">sys</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">time</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">random</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">hashlib</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">cdb</span>

<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">printExecTime</span>(target_function):
    <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">caller</span>(<span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kw):
        <span style="color: #008000; font-weight: bold">print</span>(<span style="color: #BA2121">&quot;Start : </span><span style="color: #BB6688; font-weight: bold">%s</span><span style="color: #BA2121">&quot;</span> <span style="color: #666666">%</span> target_function<span style="color: #666666">.</span>__name__)
        start_time <span style="color: #666666">=</span> time<span style="color: #666666">.</span>time()
        result <span style="color: #666666">=</span> target_function(<span style="color: #666666">*</span>args, <span style="color: #666666">**</span>kw)
        <span style="color: #008000; font-weight: bold">print</span>(<span style="color: #BA2121">&quot;End   : </span><span style="color: #BB6688; font-weight: bold">%8.4f</span><span style="color: #BA2121"> secs&quot;</span> <span style="color: #666666">%</span> (time<span style="color: #666666">.</span>time() <span style="color: #666666">-</span> start_time))
        <span style="color: #008000; font-weight: bold">return</span> result

    <span style="color: #008000; font-weight: bold">return</span> caller


<span style="color: #AA22FF">@printExecTime</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">addRecords</span>(cdb_builder):
    <span style="color: #008000; font-weight: bold">for</span> i <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">xrange</span>(<span style="color: #666666">1000000</span>):
        cdb_builder<span style="color: #666666">.</span>add(<span style="color: #008000">str</span>(i),
                        hashlib<span style="color: #666666">.</span>sha1(<span style="color: #BA2121">&quot;</span><span style="color: #BB6688; font-weight: bold">%s%s</span><span style="color: #BA2121">&quot;</span> <span style="color: #666666">%</span> (time<span style="color: #666666">.</span>time(),
                                               random<span style="color: #666666">.</span>random()))<span style="color: #666666">.</span>hexdigest())


<span style="color: #AA22FF">@printExecTime</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">finalize</span>(cdb_builder):
    cdb_builder<span style="color: #666666">.</span>finish()


<span style="color: #AA22FF">@printExecTime</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">sequentialReadTest</span>(cdb_reader):
    <span style="color: #008000; font-weight: bold">for</span> i <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">xrange</span>(<span style="color: #666666">1000000</span>):
        value <span style="color: #666666">=</span> cdb_reader<span style="color: #666666">.</span>get(<span style="color: #008000">str</span>(i))


<span style="color: #AA22FF">@printExecTime</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">randomReadTest</span>(cdb_reader):
    <span style="color: #008000; font-weight: bold">for</span> i <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">xrange</span>(<span style="color: #666666">1000000</span>):
        value <span style="color: #666666">=</span> cdb_reader<span style="color: #666666">.</span>get(<span style="color: #008000">str</span>(random<span style="color: #666666">.</span>randint(<span style="color: #666666">0</span>, <span style="color: #666666">1000000</span>)))


<span style="color: #AA22FF">@printExecTime</span>
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">iterationTest</span>(cdb_reader):
    key <span style="color: #666666">=</span> cdb_reader<span style="color: #666666">.</span>firstkey()
    <span style="color: #008000; font-weight: bold">while</span> key <span style="color: #AA22FF; font-weight: bold">is</span> <span style="color: #AA22FF; font-weight: bold">not</span> <span style="color: #008000">None</span>:
        value <span style="color: #666666">=</span> cdb_reader<span style="color: #666666">.</span>getall(key)
        <span style="color: #408080; font-style: italic"># do something...</span>
        key <span style="color: #666666">=</span> cdb_reader<span style="color: #666666">.</span>nextkey()


<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">main</span>():
    cdb_file_name <span style="color: #666666">=</span> <span style="color: #BA2121">&quot;/tmp/test.cdb&quot;</span>
    tmp_file_name <span style="color: #666666">=</span> <span style="color: #BA2121">&quot;/tmp/test.cdb.tmp&quot;</span>

    builder <span style="color: #666666">=</span> cdb<span style="color: #666666">.</span>cdbmake(cdb_file_name, tmp_file_name)

    addRecords(builder)
    finalize(builder)

    <span style="color: #008000; font-weight: bold">del</span> builder

    reader <span style="color: #666666">=</span> cdb<span style="color: #666666">.</span>init(cdb_file_name)

    sequentialReadTest(reader)
    randomReadTest(reader)
    iterationTest(reader)


<span style="color: #008000; font-weight: bold">if</span> __name__ <span style="color: #666666">==</span> <span style="color: #BA2121">&quot;__main__&quot;</span>:
    main()
</pre></div>
<p>目の前にあるワークステーションとして使っているマシンでの実行結果はこのようになりました。</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">ransui@capella<span style="color: #19177C">$ </span>python cdbexample.py
Start : addRecords
End   :   6.9454 secs
Start : finalize
End   :   0.8811 secs
Start : sequentialReadTest
End   :   0.7764 secs
Start : randomReadTest
End   :   3.4778 secs
Start : iterationTest
End   :   1.4590 secs
</pre></div>
<p>バックグラウンドで色々動かしているマシンなので、最高速は出ていませんが100万エントリの操作スピードとしてはまずまずでしょうか。ちなみにこの時のCDBファイルのサイズは67MBほどでした。</p>
<p>このソースコードを見て分かることは、CDBの生成と参照が完全に分離されているということです。データを追加したら最後に<tt class="docutils literal">finish()</tt>メソッドを呼びます。このメソッドが最終的なCDBファイルをディスク上に生成します。生成が完了したら、生成用オブジェクトはもはや不要です。なぜならCDBはいったん生成したデータの修正はできないためです。</p>
<p>上の例では値へのアクセスは<tt class="docutils literal">cdb_reader.get(KEY)</tt>のようにメソッド呼び出しを使っていますが、<tt class="docutils literal">cdb_reader[KEY]</tt>のように辞書風にアクセスすることもできます。イテレーションは最初のキーを取得してから、次々とキーを取り出していくスタイルになっています。Pythonのイテレーションプロトコルに対応するためには、<tt class="docutils literal">__iter__</tt>とか<tt class="docutils literal">__next__</tt>メソッドを定義したクラスでラップしてしまったり、<tt class="docutils literal">yield</tt>を使ってジェネレータにしてしまうのもいいでしょう。</p>
<p>CDBの特徴のうち、1つのキーに複数の値を割り当てられるという性質は、色々な目的に使える便利な機能です。数え上げと簡単な検索の例を見てみましょう。</p>
<p>例題:</p>
<blockquote>
<p>あるログファイルがあって、以下のようなフィールドがTABで区切られている。</p>
<table border="1" class="docutils">
<colgroup>
<col width="33%" />
<col width="67%" />
</colgroup>
<tbody valign="top">
<tr><td>Column No.</td>
<td>内容</td>
</tr>
<tr><td>0</td>
<td>ユーザID</td>
</tr>
<tr><td>1</td>
<td>時刻情報 (hh:mm:ss 形式の文字列)</td>
</tr>
<tr><td>2</td>
<td>検索語</td>
</tr>
</tbody>
</table>
<p>このファイルはログデータからあるプログラムによって毎日1ファイルずつアーカイブサーバ上に作成される。ディスク領域を節約するためにファイルはbzip2形式で圧縮されているこのような条件下で、ログを処理して以下のようなデータを得たい。</p>
<ol class="arabic simple">
<li>ユーザIDのバリエーション数の正確な値</li>
<li>ある特定のユーザIDを指定して、そのユーザが何回検索したか、また検索語は何であったか。</li>
</ol>
<p>ファイルは全体で1200万行あり、ユーザIDのバリエーションは100万種類くらいと見積もられている。検索語の平均長は400bytesである。ファイルはbzip2圧縮された状態のまま取り扱いたい。ディスク容量を節約したいのでインデックスファイルは巨大化させたくない。</p>
</blockquote>
<p>このような場合、ユーザIDをキーにしたKey-Valueマップを作ればOKということは分かります。単純にKeyをユーザIDとして、時刻情報と検索語をValueとしたCDBデータベースを構築してしまえば問題は解決できますが、これだと容量の大半を占める検索語をデータベースに取り込んでしまっています。</p>
<p>CDBはコンパクトなストレージファイルを生成するといっても、検索のためのオーバーヘッドがありますから、データそのものをCDBに入れてしまっては、ディスク容量を節約するためにデータファイル自体をbzip2圧縮している意味がなくなってしまいます。</p>
<p>問題のポイントは、ユーザIDが指定されたときに、<em>実データを素早く参照できればOK</em>という部分と、<em>実データファイルは更新されない</em>という点です。これを踏まえてちょっとしたプログラムを書いてみます。</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"># -*- coding: utf-8 -*-</span>
import sys
import os.path
import bz2
import cdb

class BZ2Index<span style="color: #666666">(</span>object<span style="color: #666666">)</span>:
    def __init__<span style="color: #666666">(</span>self, <span style="color: #19177C">target_file_name</span><span style="color: #666666">=</span>None, <span style="color: #19177C">key_column</span><span style="color: #666666">=</span>0<span style="color: #666666">)</span>:
        self.target_file_name <span style="color: #666666">=</span> target_bz2_file_name
        self.index_file_name <span style="color: #666666">=</span> <span style="color: #BA2121">&quot;%s.cdb&quot;</span> % os.path.splitext<span style="color: #666666">(</span>target_file_name, <span style="color: #BA2121">&quot;.bz2&quot;</span><span style="color: #666666">)[</span>0<span style="color: #666666">]</span>
        self.key_column <span style="color: #666666">=</span> key_column


    def buildIndex<span style="color: #666666">(</span>self<span style="color: #666666">)</span>:
        <span style="color: #19177C">cdb_builder</span> <span style="color: #666666">=</span> cdb.cdbmake<span style="color: #666666">(</span>self.index_file_name,
                                  self.index_file_name + <span style="color: #BA2121">&quot;.tmp&quot;</span><span style="color: #666666">)</span>
        <span style="color: #19177C">pos</span> <span style="color: #666666">=</span> 0
        <span style="color: #19177C">archive_file</span> <span style="color: #666666">=</span> bz2.BZ2File<span style="color: #666666">(</span>self.target_file_name, <span style="color: #BA2121">&quot;r&quot;</span><span style="color: #666666">)</span>

        <span style="color: #008000; font-weight: bold">for </span>line in archive_file:
            <span style="color: #19177C">key</span> <span style="color: #666666">=</span> line.strip<span style="color: #666666">()</span>.split<span style="color: #666666">(</span><span style="color: #BA2121">&quot;\t&quot;</span><span style="color: #666666">)[</span>self.key_column<span style="color: #666666">]</span>
            cdb_builder.add<span style="color: #666666">(</span>key, str<span style="color: #666666">(</span>pos<span style="color: #666666">))</span>
            <span style="color: #19177C">pos</span> <span style="color: #666666">=</span> target_file.tell<span style="color: #666666">()</span>

        cdb_builder.finish<span style="color: #666666">()</span>
        del cdb_builder
        archive_file.close<span style="color: #666666">()</span>


    def openIndex<span style="color: #666666">(</span>self<span style="color: #666666">)</span>:
        self.cdb_reader <span style="color: #666666">=</span> cdb.init<span style="color: #666666">(</span>self.index_file_name<span style="color: #666666">)</span>
        self.archive_file <span style="color: #666666">=</span> bz2.BZ2File<span style="color: #666666">(</span>self.target_file_name, <span style="color: #BA2121">&quot;r&quot;</span><span style="color: #666666">)</span>


    def getRecords<span style="color: #666666">(</span>self, key<span style="color: #666666">)</span>:
        <span style="color: #19177C">result</span> <span style="color: #666666">=</span> list<span style="color: #666666">()</span>
        <span style="color: #008000; font-weight: bold">for </span>position in <span style="color: #666666">(</span>int<span style="color: #666666">(</span>pos<span style="color: #666666">)</span> <span style="color: #008000; font-weight: bold">for </span>pos in self.cdb_reader.getall<span style="color: #666666">(</span>key<span style="color: #666666">))</span>:
            self.archive_file.seek<span style="color: #666666">(</span>position, 0<span style="color: #666666">)</span>
            result.append<span style="color: #666666">(</span>self.archive_file.readline<span style="color: #666666">())</span>

        <span style="color: #008000; font-weight: bold">return </span>result
</pre></div>
<p>このクラスはbzip2圧縮されたTSVファイルに索引をつけて、指定したキーに対応する実データを取得する機能を提供します。インデックスであるCDBには、キーに対応する値として、実データファイル先頭からのオフセット値を格納します。こうすることで、インデックスには整数(文字列化されていますが)のみが格納されるので、コンパクトに仕上がります。</p>
<p>データを読み出す場合は、インデックスをキーでひくと実データのオフセット位置のリストが返ってくるので、それぞれについてseekして1行読み出します。ISAMに似ていますが、実データの編成は固定長レコードではなく、行指向となっています。</p>
<img alt="./images/data-w-cdb02.png" src="./images/data-w-cdb02.png" />
<p>CDBは1つのキーに対して、複数の<tt class="docutils literal">add</tt>があると値の置き換えではなく追加として処理し、<tt class="docutils literal">getall</tt>メソッドを使うことで複数の値をリストとして取り出すことができます。この性質をうまく使えばキーのユニーク化や、数え上げを手軽に処理できます。わざわざRDBMSやKVSなどのクライアントサーバシステムを準備しなくても、こんな単純な仕組みと少ない道具建てでそれなりにこなせるものです。もっともより本格的な分析とかになれば話は別です。戦車を相手にするときは迷わずバズーカ砲を使いましょう。</p>
</div>
</div>
<div class="section" id="id5">
<h1>ちょっとした道具を自分で作ってみることの大切さ</h1>
<p>今回は、CDBをつかってものすごく単純なインデックス付きファイル処理について紹介してみました。例示したプログラム片は「弓矢」レベルですが、社内で使っている道具は、もう少し色々できるようになっていて、「散弾銃」くらいになっています。</p>
<p>このようなデータの取扱い方法は、それこそ大昔から普通に行われてきたことですが、最近はRDBMSやKVSや分散システムなど、抽象度の高いインタフェースの裏に隠れてしまっています。さらに言えば、CDBそれ自身もよりハッシュマップの詳細な実装に対するを抽象度の高いインタフェースなわけです。</p>
<p>これらの便利につかえる道具について、どんな風に動いているのか、どんな原理なのかということを理解しておくことは、大きなフレームワークをより良く使いこなすための、大きなアドバンテージになるはずです。そして、それを理解するためにはプログラムを自分で実際に書いてみるのが一番いいことだと信じています。もちろん「道具を使うコード」ではなく「道具を実現するコード」をです。</p>
<p>最初に作った「弓矢」から「散弾銃」レベルまで拡張していくと、おおよそ似たようなシステムのコアな部分や、必須の機能、実現が難しい所、性能のボトルネックなどが見えてくるようになってきます。</p>
<pre class="literal-block">
「鴨を撃つのにご自慢のバズーカ砲持ってこなくていいよ。
　　だいたい、俺っちの車には、そのデカいケース載らないよ。
　　　前に作った散弾銃があって、ちょうど良さそうなんだ。
        調整するから5分待ってて！」
</pre>
<p>なんていうこともできるようになるわけです。こうして世界中のHackerたちが作り出した、より洗練された道具についてもよく理解できるようになり、本当は全然プアなのに「俺様の作ったライブラリ・フレームワーク最強」な自前主義や、「よくわからないけど、なんか流行っている・実績あるらしいから、これ使っとけ」的な外部依存体質に陥らないための視点も養成できるのではないかと思っています。</p>
<p class="footnote"><strong>磯　蘭水 (Ransui Iso)</strong><br />
株式会社クロスリスティング（<a class="reference external" href="http://www.xlisting.co.jp/">http://www.xlisting.co.jp/</a>）<br />
技術戦略グループ・ディレクター<br />
PC-6001mk2のBASICでプログラミングデビュー。
その後Z80アセンブリ、C on MS-DOS,SunOS、CommonLisp(GCL) on SunOSと変遷して、最近10年はPython on GNU/Linuxがメイン環境。<br />
最近またCommon Lispに回帰して仕事に使おうと画策中のアラフォー ソフトウェアデベロッパ。@ransui/twitter, Ransui Iso/facebook</p>
]]>
    </content>
</entry>

<entry>
    <title>Python 3 のオブジェクト文字列表現</title>
    <link rel="alternate" type="text/html" href="http://www.oreilly.co.jp/community/blog/2010/12/string-reprensation-in-python3.html" />
    <id>tag:www.oreilly.co.jp,2010:/community/blog//4.709</id>

    <published>2010-12-08T06:00:00Z</published>
    <updated>2010-12-08T05:22:35Z</updated>

    <summary>2008年にリリースされたPython 3。さまざまな機能が追加、更新されている中に私たち日本人にとっては嬉しい機能が追加されています。今回はその機能を提案・設計したご本人に解説をご寄稿いただきました...</summary>
    <author>
        <name>Atsuo Ishimoto</name>
        
    </author>
    
        <category term="Contributors Post" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="ja" xml:base="http://www.oreilly.co.jp/community/blog/">
        <![CDATA[<p class="lead">2008年にリリースされたPython 3。さまざまな機能が追加、更新されている中に私たち日本人にとっては嬉しい機能が追加されています。今回はその機能を提案・設計したご本人に解説をご寄稿いただきました。<br />また、本記事の内容は<a href="http://atnd.org/events/2906">Python Hack-a-thon 2010.07</a>でのプレゼンテーションを元にしています</p>
<div class="section" id="python-2-x">
<h1>Python 2.x までのオブジェクト文字列表現</h1>
<p>Pythonを使ってプログラムを開発していると、デバッグ中などにこんな感じの文字列をよく目にします。</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #666666">&gt;&gt;&gt;</span> <span style="color: #BA2121">&quot;abc</span><span style="color: #BB6622; font-weight: bold">\t</span><span style="color: #BA2121">def&quot;</span>
<span style="color: #BA2121">&#39;abc</span><span style="color: #BB6622; font-weight: bold">\t</span><span style="color: #BA2121">def&#39;</span>
<span style="color: #666666">&gt;&gt;&gt;</span> datetime<span style="color: #666666">.</span>datetime<span style="color: #666666">.</span>now()
datetime<span style="color: #666666">.</span>datetime(<span style="color: #666666">2010</span>, <span style="color: #666666">7</span>, <span style="color: #666666">9</span>, <span style="color: #666666">13</span>, <span style="color: #666666">37</span>, <span style="color: #666666">49</span>, <span style="color: #666666">107000</span>)

</pre></div>
<p><tt class="docutils literal">&quot;abc\tdef&quot;</tt>や<tt class="docutils literal">datetime.datetime.now()</tt>という式を評価した結果が、<tt class="docutils literal">'abc\tdef'</tt>と<tt class="docutils literal">datetime.datetime(2010, 7, 9, 13, 37, 49, 107000)</tt>のように表示されています。このような、Pythonの値をデバッグ用に読みやすく整形した文字列を「オブジェクトの文字列表現」と言います。</p></div>]]>
        <![CDATA[<div><p>オブジェクト文字列表現はできるだけ表示するオブジェクトを表すPythonの式と同じになるように生成されており、たとえばリストオブジェクトや辞書オブジェクトは</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #666666">&gt;&gt;&gt;</span> [<span style="color: #666666">1</span>,<span style="color: #666666">2</span>,<span style="color: #666666">3</span>]
[<span style="color: #666666">1</span>, <span style="color: #666666">2</span>, <span style="color: #666666">3</span>]

<span style="color: #666666">&gt;&gt;&gt;</span> {<span style="color: #666666">1</span>:<span style="color: #BA2121">&#39;abc&#39;</span>, <span style="color: #666666">2</span>:<span style="color: #BA2121">&#39;def&#39;</span>}
{<span style="color: #666666">1</span>: <span style="color: #BA2121">&#39;abc&#39;</span>, <span style="color: #666666">2</span>: <span style="color: #BA2121">&#39;def&#39;</span>}

</pre></div>
<p>のように、そのままPythonの式として使える形で出力されます。</p>
<p>しかし、これまで日本語文字列を含むデータを出力する時には、あまり使いやすいとは言えませんでした。</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #666666">&gt;&gt;&gt;</span> <span style="color: #BA2121">&quot;はろー&quot;</span>
<span style="color: #BA2121">&#39;</span><span style="color: #BB6622; font-weight: bold">\x82\xcd\x82\xeb\x81</span><span style="color: #BA2121">[&#39;</span>
</pre></div>
<p>のように、ASCII文字以外の文字は、すべて'xXX'の形式でエスケープされて出力されてしまっていたからです<a class="footnote-reference" href="#id6" id="id1">[1]</a>。せっかくデータが出力されても、これではいちいちPython上で</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #666666">&gt;&gt;&gt;</span> <span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">&#39;</span><span style="color: #BB6622; font-weight: bold">\x82\xcd\x82\xeb\x81</span><span style="color: #BA2121">[&#39;</span>

<span style="border: 1px solid #FF0000">はろー</span>
</pre></div>
<p>としなければ、どのようなデータなのか確認することができませんでした。</p>
<p>文字列データのデバッグであれば<tt class="docutils literal">print</tt>すれば良いのですが、</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #666666">&gt;&gt;&gt;</span> <span style="color: #008000">open</span>(<span style="color: #BA2121">&quot;はろー&quot;</span>)
Traceback (most recent call last):
File <span style="color: #BA2121">&quot;&lt;stdin&gt;&quot;</span>, line <span style="color: #666666">1</span>, <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #666666">&lt;</span>module<span style="color: #666666">&gt;</span>

<span style="color: #D2413A; font-weight: bold">IOError</span>: [Errno <span style="color: #666666">2</span>] No such <span style="color: #008000">file</span> <span style="color: #AA22FF; font-weight: bold">or</span> directory: <span style="color: #BA2121">&#39;</span><span style="color: #BB6622; font-weight: bold">\x82\xcd\x82\xeb\x81</span><span style="color: #BA2121">[&#39;</span>
</pre></div>
<p>のように、エラーメッセージなどに含まれる文字列までエスケープされてしまうため、対処が非常に面倒でした。</p>
</div>
<div class="section" id="python-3-0">

<h1>Python 3.0からのオブジェクト文字列表現</h1>
<p>そこでPython 3.0以降では、文字列中の非ASCII文字もエスケープしないように変更され、日本語文字列を出力すれば、日本語としてそのまま出力できるようになっています。</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #666666">&gt;&gt;&gt;</span> <span style="color: #BA2121">&quot;はろー&quot;</span>
<span style="color: #BA2121">&#39;はろー&#39;</span>
<span style="color: #666666">&gt;&gt;&gt;</span> <span style="color: #008000">open</span>(<span style="color: #BA2121">&quot;はろー&quot;</span>)
Traceback (most recent call last):
  File <span style="color: #BA2121">&quot;&lt;stdin&gt;&quot;</span>, line <span style="color: #666666">1</span>, <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #666666">&lt;</span>module<span style="color: #666666">&gt;</span>

<span style="color: #D2413A; font-weight: bold">IOError</span>: [Errno <span style="color: #666666">2</span>] No such <span style="color: #008000">file</span> <span style="color: #AA22FF; font-weight: bold">or</span> directory: <span style="color: #BA2121">&#39;はろー&#39;</span>
</pre></div>
<p>ただし、タブや改行のような空白文字などは、Python 2と同様にエスケープされます。</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #666666">&gt;&gt;&gt;</span> <span style="color: #BA2121">&quot;は</span><span style="color: #BB6622; font-weight: bold">\t</span><span style="color: #BA2121">ろ</span><span style="color: #BB6622; font-weight: bold">\t</span><span style="color: #BA2121">ー&quot;</span>

<span style="color: #BA2121">&#39;は</span><span style="color: #BB6622; font-weight: bold">\t</span><span style="color: #BA2121">ろ</span><span style="color: #BB6622; font-weight: bold">\t</span><span style="color: #BA2121">ー&#39;</span>
</pre></div>
<p>これで日本語を扱うプログラムの開発は楽になったのですが、実はまだ問題があります。</p>
<p>Python2では全てのオブジェクト文字列表現はASCII文字しか含まなかったため、通常の環境では出力時にエラーが発生することはありませんでした。しかし、Python3では文字列に使用中のコンソールでサポートされていない文字が含まれていると、エラーが発生するようになってしまったのです。</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #666666">&gt;&gt;&gt;</span> <span style="color: #BA2121">&quot;</span><span style="color: #BB6622; font-weight: bold">\u0550</span><span style="color: #BA2121">&quot;</span> <span style="color: #408080; font-style: italic"># ARMENIAN CAPITAL LETTER REH</span>
Traceback (most recent call last):
  File <span style="color: #BA2121">&quot;&lt;stdin&gt;&quot;</span>, line <span style="color: #666666">1</span>, <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #666666">&lt;</span>module<span style="color: #666666">&gt;</span>

<span style="color: #D2413A; font-weight: bold">UnicodeEncodeError</span>: <span style="color: #BA2121">&#39;cp932&#39;</span> codec can<span style="color: #BA2121">&#39;t encode character &#39;</span>\u0550<span style="color: #BA2121">&#39; in position 1:</span>
 illegal multibyte sequence
</pre></div>
<p>これではデータの中身によってデバッグ時にエラーが発生してしまうため、非常に不便です。このため、Python3ではコンソールでサポートされている文字はそのままで、サポートされていない文字のみ16進形式にエスケープする方法が用意されました。</p>
<p>Pythonを起動する時、環境変数<tt class="docutils literal">PYTHONIOENCODING</tt>を<tt class="docutils literal">エンコーディング名:backslashreplace</tt>と指定すると、エンコーディング名でサポートされない文字だけが16進形式でエスケープして出力されます。</p>

<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #19177C">$ </span><span style="color: #008000">export </span><span style="color: #19177C">PYTHONIOENCODING</span><span style="color: #666666">=</span>euc-jp:backslashreplace
<span style="color: #19177C">$ </span>python
&gt;&gt;&gt; <span style="color: #BA2121">&quot;はろー \u0550&quot;</span> <span style="color: #408080; font-style: italic"># ARMENIAN CAPITAL LETTER REH</span>
<span style="color: #BA2121">&quot;はろー \u0550&quot;</span>
</pre></div>
<p><tt class="docutils literal">backslashreplace</tt>はUnicodeエラーハンドラと呼ばれ、Unicode変換エラーが発生した場合の対処方法を指定します。通常のファイルではデフォルトで<tt class="docutils literal">strict</tt>となっており、変換エラーがあればそのまま<tt class="docutils literal">UnicodeEncodeError</tt>例外を送出します。<tt class="docutils literal">backslashreplace</tt>は、例外を送出せずに16進形式にエスケープして出力するための指定です。</p>

<p>標準出力ファイルのエンコードとエラーハンドラは環境変数<tt class="docutils literal">PYTHONIOENCODING</tt>で指定しますが、標準エラー出力のエラーハンドラは<tt class="docutils literal">backslashreplace</tt>固定となっており、変更することはできません。<a class="footnote-reference" href="#id7" id="id2">[2]</a></p>
<p>自分でファイルをオープンする場合は、</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #666666">&gt;&gt;&gt;</span> <span style="color: #008000">open</span>(filename, <span style="color: #BA2121">&quot;w&quot;</span>, errors<span style="color: #666666">=</span><span style="color: #BA2121">&#39;backslashreplace&#39;</span>)

</pre></div>
<p>のように、<tt class="docutils literal">errors=</tt>でエラーハンドラを指定することができます。テキストデータ出力時、どんなデータでも<tt class="docutils literal">UnicodeEncodeError</tt>が発生しないようにするのなら<tt class="docutils literal">backslashreplace</tt>や<tt class="docutils literal">ignore</tt>などのエラーハンドラを指定するようにします。</p>
<p>Python 2.xと同様に、全ての非ASCII文字をエスケープしてしまいたい場合は、組み込み関数<tt class="docutils literal">ascii()</tt>を使用します。</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #666666">&gt;&gt;&gt;</span> <span style="color: #008000; font-weight: bold">print</span>(ascii(<span style="color: #BA2121">&quot;hello, はろー&quot;</span>))

<span style="color: #BA2121">&#39;hello, </span><span style="color: #BB6622; font-weight: bold">\u306f\u308d\u30fc</span><span style="color: #BA2121">&#39;</span>
</pre></div>
</div>
<div class="section" id="python-python-enhancement-proposal">
<h1>Python 改善提案書 (Python Enhancement Proposal)</h1>
<p>ここで解説したPython3.xでの文字列表現の変更は、実は私が提案し、実現した機能です。たまたま<a class="reference external" href="http://mail.python.org/mailman/listinfo/python-dev">Python開発者メーリングリスト</a>でオブジェクトの文字出力についての話題が上がっているのを目にし、そのとき思いついた日本語出力が可能になる実装方法をメールに書き、簡単なパッチを作成したのが始まりでした。</p>
<p>その後、あわよくばそのままPython本体に取り込んでもらえるのではないかと期待していたのですが、Python2との互換性面で影響が大きいため<strong>PEP</strong>が必要と言われてしまいました。PEPとは 「Python改善提案書」（Python Enhancement Proposal）の略で、Python言語の改訂や標準モジュールの追加など、影響の大きな修正を行う場合には作成することが要求されます。PEPには改善の目的や具体的な実装方法、互換性への影響などを詳しく記述し、開発者メーリングリストで議論を求めます。議論の過程で得られた意見や改善点もPEPに記録し、議論が落ち着いたところでPythonの産みの親であるGuido van Rossum氏の裁定によって導入の可否が決定されます。</p>
<p>Guido van Rossum氏は<strong>慈悲深き終身独裁官</strong>（Benevolent Dictator For Life:BDFL）としてPython開発者コミュニティに君臨しています。Guidoの裁定は&quot;BDFL裁定&quot;（BDFL pronouncement）と呼ばれ、最終的な判断として尊重されます。議論で合意に達することができなかった場合でも、投票は行われません。開発者たちはメーリングリストで自分の意見を表明しますが、賛否の数に関係なくBDFLの判断によって裁定が下されます。</p>

<p>PEPは2000年頃から導入された制度で、修正の目的や議論をきちんと文書化することによって正しいBDFL裁定を下し、後からなぜこのような修正が加えられたのかを明確に記録することができるようになりました。またPEPが認められなかった場合でも、同じ提案が何度も何度も繰り返し提出されることを防いだり、却下されたPEPを元にもっと優れたPEPを作成して再度チャレンジすることができるようになりました。<a class="footnote-reference" href="#id8" id="id3">[3]</a></p>
<p>けっこう気軽にパッチを投稿したつもりが「PEP書け」と言われてちょっとうろたえてしまった私でしたが、ここまで来ては後に引けません。英語はかなり苦手ですがPEPのドラフト版を作成し、PEP管理者にPEP番号の発行を依頼します。このPEPは「3138」という番号で登録され、Pythonのウエブサイト上で公開されました。<a class="footnote-reference" href="#id10" id="id4">[4]</a></p>
<p>次にこのPEPをPythonの開発者メーリングリストに投稿し、コメントを求めます。ヨーロッパ語圏のユーザにとってはあまりメリットが無く、既存のアプリケーションへの影響も大きな修正だったので反対もありましたが、おおむね賛同を得ることができました。メーリングリストで上がった反対意見や改善点は順次PEPに反映し、パッチもあわせて更新を繰り返します。この間、英語圏の開発者が何度もPEPを添削してくれて、ちゃんと読める英語に直すことができました。議論がある程度進んだところでGuidoの&quot;BDFL裁定&quot;により承認され、Pythonのソースツリーにめでたく登録されることになりました。</p>
</div>
<div class="section" id="id5">
<h1>英語でのフィードバック</h1>
<p>PEPの有無にかかわらず、利用者からPython開発者たちへのフィードバックを送るのはPythonの発展のために非常に重要です。特にアジア圏からのフィードバックはあまり多くないため、Python開発者たちから高い関心を持って読んでもらえることが多いようです。</p>
<p>フィードバックにはある程度の英語力は必要となりますが、技術的なバックボーンがしっかりしていれば私と同程度の英語力―おそらく中学生程度―で十分ではないでしょうか。みんな下手くそなメールを読むのは慣れていますし、相手から揚げ足取りがある訳ではありません。議論の目的はPythonを改善することで、議論の相手もその気持ちは一緒です。利害の対立があるわけではありませんから、下手に議論や文章のテクニックを使ったりする必要もありません。文章がつたなくても、こちらに良い点があれば、相手が積極的に拾い上げてくれます。</p>

<p>長年、色々な英語のメーリングリストを読んできましたが、非英語圏の投稿者の文法や語彙を批判するメールというのは一度も目にした記憶がありません。コミュニケーションですから、できるだけわかりやすい英語を書くように努力する必要はあるでしょうが、完璧な英語を目指すのは無意味です。時制とか定冠詞とか三単現のSだとか、細かいところで間違えるのは仕方ない、とあきらめてしまえば英語を書くのは楽になりますし、文法にこだわるよりもその時間を議論の論理構成やポイントを把握するために使った方が良い結果につながるのではないでしょうか。</p>
<p>昔から言われる話ですが、日本にはかなりの数のオープンソース開発者・利用者がいる割には、バグ報告をしたりパッチを送ったりという活動があまり活発ではありません。Pythonに限らず、いろいろな開発コミュニティにもっと気軽に参加してみましょう。英語でコミュニケーションを取る経験をほんの何回か積むだけで、「あ、なにを怖じ気づいていたんだろう」という気持ちになれると思います。</p>
<p class="footnote">
執筆者紹介:<br />
<strong>石本敦夫 &#64;atsuoishimoto</strong><br />
アクシスソフト（株） シニア アーキテクト<br />
日本におけるPythonのメーリングリストやユーザ会を立ち上げた、古株Pythonユーザ。現在もPythonによるメール・情報管理ツールInfoPile（<a class="reference external" href="http://www.infopile.jp/">http://www.infopile.jp/</a>）の開発などを活発に行っている。</p>
<table class="docutils footnote" frame="void" id="id6" rules="none">
<colgroup><col class="label" /><col /></colgroup>
<tbody valign="top">
<tr><td class="label"><a class="fn-backref" href="#id1">[1]</a></td><td>ここで示している出力例は、WindowsなどシフトJIS環境での一例です</td></tr>
</tbody>
</table>
<table class="docutils footnote" frame="void" id="id7" rules="none">
<colgroup><col class="label" /><col /></colgroup>
<tbody valign="top">
<tr><td class="label"><a class="fn-backref" href="#id2">[2]</a></td><td>標準出力ファイルのエラーハンドラに指定できるのは、Codec基底クラスのerror文字列引数に与えられる値です。 <a class="reference external" href="http://www.python.jp/doc/2.6/library/codecs.html#codec">http://www.python.jp/doc/2.6/library/codecs.html#codec</a> を参照</td></tr>

</tbody>
</table>
<table class="docutils footnote" frame="void" id="id8" rules="none">
<colgroup><col class="label" /><col /></colgroup>
<tbody valign="top">
<tr><td class="label"><a class="fn-backref" href="#id3">[3]</a></td><td><a class="reference external" href="http://www.python.org/dev/peps/">http://www.python.org/dev/peps/</a> を参照。またPEP誕生の経緯は『<a class="reference external" href="http://www.oreilly.co.jp/books/9784873114712/">言語設計者たちが考えること</a>』の2章、Guido van Rossum氏へのインタビュー中にも収録されています</td></tr>
</tbody>
</table>
<table class="docutils footnote" frame="void" id="id10" rules="none">
<colgroup><col class="label" /><col /></colgroup>
<tbody valign="top">

<tr><td class="label"><a class="fn-backref" href="#id4">[4]</a></td><td><a class="reference external" href="http://www.python.org/dev/peps/pep-3138/">PEP3138: String representation in Python 3000</a></td></tr>
</tbody>
</table>
</div>]]>
    </content>
</entry>

<entry>
    <title>JSON形式による書誌情報の提供をはじめました</title>
    <link rel="alternate" type="text/html" href="http://www.oreilly.co.jp/community/blog/2010/11/bibliographical-info-in-json.html" />
    <id>tag:www.oreilly.co.jp,2010:/community/blog//4.685</id>

    <published>2010-11-24T09:00:00Z</published>
    <updated>2010-11-25T09:24:27Z</updated>

    <summary>「O&apos;Reilly Japan Ebook Store」サイトのオープンからもうすぐ2年経過します。販売タイトルも、当初はわずか20タイトルほどだったのですが、現在では160タイトルほどになり、カタロ...</summary>
    <author>
        <name>O&apos;Reilly Japan</name>
        <uri>http://www.oreilly.co.jp/</uri>
    </author>
    
        <category term="Inside O&apos;Reilly" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="ja" xml:base="http://www.oreilly.co.jp/community/blog/">
        <![CDATA[<span class="mt-enclosure mt-enclosure-image" style="display: inline;"><a href="http://www.oreilly.co.jp/community/blog/renamed_ebook1.html" onclick="window.open('http://www.oreilly.co.jp/community/blog/renamed_ebook1.html','popup','width=400,height=400,scrollbars=no,resizable=no,toolbar=no,directories=no,location=no,menubar=no,status=no,left=0,top=0'); return false"><img src="http://www.oreilly.co.jp/community/blog/assets_c/2010/10/renamed_ebook-thumb-200x200.jpg" width="200" height="200" alt="renamed_ebook.jpg" class="mt-image-left" style="float: left; margin: 0 20px 20px 0;" /></a></span><p>「<a href="http://www.oreilly.co.jp/ebook/">O'Reilly Japan Ebook Store</a>」サイトのオープンからもうすぐ2年経過します。販売タイトルも、当初はわずか20タイトルほどだったのですが、現在では160タイトルほどになり、カタログに掲載されている書籍の半分弱くらいになっています。</p>
<p>ところでこのEbook、ご購入後にサイトからダウンロードしていただくと、ファイル名が「&lt;16進数の羅列&gt;-&lt;書籍のISBN&gt;.pdf」という形式になっています。もちろん変更はできるのですが、そのまま電子書籍リーダーなどに読み込むと、画面が英数字の羅列だらけになってしまいます。</p>
<p>かく言う私たちも、イベント出展にサンプルのEbookを持って行くことがあるのですが、イベント会場でISBNだけがズラ―っと並んだ画面をお見せするのをとても心苦しく思っていました。</p>
<p>以前は手作業でファイル名を変更していたのですが、100タイトルを超えた辺りで心が折れました。Amazon APIを使って書名を取得すればいいことは分かっていたのですが、発行元がそれをしてしまったら負けな気がします。</p>
<p>そこでWebサイトの書誌情報を以下のようなURLで取得できるように、テンプレートを1つ追加しました。ファイル名をご覧いただくと分かるように、簡単な書誌情報をJSON形式でご提供しています。</p>
<p><a class="reference external" href="http://www.oreilly.co.jp/books/9784873114569/biblio.json">http://www.oreilly.co.jp/books/9784873114569/biblio.json</a></p>]]>
        <![CDATA[<p>使い方はこんな感じです（Python2.6を使っています）。</p>
<div class="highlight"><pre><span style="color: #408080; font-style: italic">#/usr/bin/env python</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">os</span><span style="color: #666666">,</span> <span style="color: #0000FF; font-weight: bold">sys</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">shutil</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">urllib</span>
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">json</span>

<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">main</span>(src):
    <span style="color: #008000; font-weight: bold">try</span>:
        <span style="color: #008000; font-weight: bold">if</span> os<span style="color: #666666">.</span>access(<span style="color: #BA2121">&#39;./dist/&#39;</span>, os<span style="color: #666666">.</span>F_OK) <span style="color: #666666">!=</span> <span style="color: #008000">True</span>:
            os<span style="color: #666666">.</span>mkdir(<span style="color: #BA2121">&#39;./dist&#39;</span>)
        isbn <span style="color: #666666">=</span> src<span style="color: #666666">.</span>split(<span style="color: #BA2121">&#39;.&#39;</span>)[<span style="color: #666666">0</span>]<span style="color: #666666">.</span>split(<span style="color: #BA2121">&#39;-&#39;</span>)[<span style="color: #666666">-1</span>]
        biblio <span style="color: #666666">=</span> json<span style="color: #666666">.</span>load(urllib<span style="color: #666666">.</span>urlopen( \
            <span style="color: #BA2121">&#39;http://www.oreilly.co.jp/books/</span><span style="color: #BB6688; font-weight: bold">%s</span><span style="color: #BA2121">/biblio.json&#39;</span> <span style="color: #666666">%</span> isbn)<span style="color: #666666">.</span>fp)
        dist <span style="color: #666666">=</span> <span style="color: #BA2121">&quot;./dist/</span><span style="color: #BB6688; font-weight: bold">%s</span><span style="color: #BA2121">.pdf&quot;</span> <span style="color: #666666">%</span> (biblio[<span style="color: #BA2121">&#39;title&#39;</span>]<span style="color: #666666">.</span>replace(<span style="color: #BA2121">&#39;:&#39;</span>,<span style="color: #BA2121">&#39;&#39;</span>))
        shutil<span style="color: #666666">.</span>copy(src, dist)
        <span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000">True</span>
    <span style="color: #008000; font-weight: bold">except</span>:
        sys<span style="color: #666666">.</span>stderr<span style="color: #666666">.</span>write(<span style="color: #BA2121">&quot;Rename fault with </span><span style="color: #BB6688; font-weight: bold">%s</span><span style="color: #BA2121">&quot;</span> <span style="color: #666666">%</span> (src))
        <span style="color: #008000; font-weight: bold">raise</span>

<span style="color: #008000; font-weight: bold">if</span> __name__<span style="color: #666666">==</span><span style="color: #BA2121">&#39;__main__&#39;</span>:
    <span style="color: #008000; font-weight: bold">if</span> <span style="color: #008000">len</span>(sys<span style="color: #666666">.</span>argv) <span style="color: #666666">&lt;=</span> <span style="color: #666666">1</span>:
        <span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">&quot;usage: ebrename.py &lt;downloaded_file&gt;&quot;</span>
    <span style="color: #008000; font-weight: bold">else</span>:
        main(sys<span style="color: #666666">.</span>argv[<span style="color: #666666">1</span>])
</pre></div>
<p>あとは以下のようにコマンドを実行すれば、distというフォルダ以下にリネームされたファイルがコピーされます。</p>
<p>コマンドプロンプト</p>
<div class="highlight"><pre><span style="color: #008000; font-weight: bold">for</span> %x in <span style="color: #666666">(</span>.<span style="color: #BB6622; font-weight: bold">\*</span>.pdf<span style="color: #666666">)</span>;
    <span style="color: #008000; font-weight: bold">do </span>python &lt;スクリプト名&gt; %x
</pre></div>

<p>bash</p>
<div class="highlight"><pre><span style="color: #008000; font-weight: bold">for </span>x in <span style="color: #BA2121">`</span>ls *.pdf<span style="color: #BA2121">`</span>
<span style="color: #008000; font-weight: bold">do</span>
<span style="color: #008000; font-weight: bold">    </span>python &lt;スクリプト名&gt; <span style="color: #19177C">$x</span>
<span style="color: #008000; font-weight: bold">done</span>
</pre></div>
<p>あとはファイルを電子書籍リーダーに転送するだけ。Amazon Kindle2のように日本語フォントを持たないデバイスの場合は、原書タイトルにリネームするとよいでしょう（ただし、上で例に挙げた『Andoid Hacks』のように、日本オリジナルのタイトルもありますのでご注意ください）。これで電子書籍リーダーの画面を、悲しい気持ちで見なくても済みそうです。</p>]]>
    </content>
</entry>

<entry>
    <title>バッファキャッシュとAIO（4）</title>
    <link rel="alternate" type="text/html" href="http://www.oreilly.co.jp/community/blog/2010/10/buffer-cache-and-aio-part4.html" />
    <id>tag:www.oreilly.co.jp,2010:/community/blog//4.662</id>

    <published>2010-10-26T02:00:00Z</published>
    <updated>2010-12-08T09:41:38Z</updated>

    <summary> 前回はPOSIX AIOとLinuxカーネルのAIOサポートについて解説しました。今回は、このAIOの使い勝手を良くするため、POSIX AIOインタフェース準拠のライブラリを作成しています。 Li...</summary>
    <author>
        <name>千住治郎</name>
        
    </author>
    
        <category term="Programmer&apos;s High" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="ja" xml:base="http://www.oreilly.co.jp/community/blog/">
        <![CDATA[<img alt="http://www.oreilly.co.jp/community/blog/images/union/pg_high_logo.png" src="http://www.oreilly.co.jp/community/blog/images/union/pg_high_logo.png" />
<p class="lead">前回はPOSIX AIOとLinuxカーネルのAIOサポートについて解説しました。今回は、このAIOの使い勝手を良くするため、POSIX AIOインタフェース準拠のライブラリを作成しています。</p>
<div class="section" id="linuxaioliblaio">
<h2>LinuxネイティブAIOライブラリliblaioの試作</h2>
<p>Linux AIOを使用する場合、現在では前述のlibaioの利用が第一候補になりますが、やや使い勝手が悪いため、本記事でPOSIX AIOインタフェース準拠のライブラリを試作してみます。Linux AIOでは<tt class="docutils literal">O_DIRECT</tt>が前提となるため、この点もやや使い勝手が悪いのですが、SSDなどメモリベースのファイルシステムもありますし、動作は非同期になりませんが<tt class="docutils literal">io_submit(2)</tt>は<tt class="docutils literal">O_DIRECT</tt>がなくとも使用可能ですから、まぁ試しにやってみましょう。</p>
<p>ライブラリ設計要点を挙げます。</p>
<div class="section" id="linux-aioposix-aio">
<h3>Linux AIOにPOSIX AIOインタフェースをかぶせる</h3>
<p>簡単に対応をとってみると<a class="reference internal" href="#id20">表3</a>の様になります。</p>
<p><span class="target" id="id20">表3</span> Linux AIOシステムコールとPOSIX AIOインタフェースの対応</p>
<table border="1" class="docutils">
<colgroup>
<col width="50%" />
<col width="50%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head">POSIX AIOインタフェース</th>
<th class="head">Linux AIOシステムコール</th>
</tr>
</thead>
<tbody valign="top">
<tr><td>aio_read</td>
<td rowspan="4">io_submit</td>
</tr>
<tr><td>aio_write</td>
</tr>
<tr><td>aio_fsync</td>
</tr>
<tr><td>lio_listio</td>
</tr>
<tr><td>aio_suspend</td>
<td rowspan="3">io_getevents</td>
</tr>
<tr><td>aio_return</td>
</tr>
<tr><td>aio_error</td>
</tr>
<tr><td>aio_cancel</td>
<td>io_cancel</td>
</tr>
</tbody>
</table>
<p><tt class="docutils literal">io_setup(2)</tt>などの前処理はライブラリ内で自動的に行いますが、プログラマが同時AIO数を拡張するなどの場合も考えられます。このため、Glibc拡張である<tt class="docutils literal">aio_init(3)</tt>を流用／対応すると同時に、<tt class="docutils literal">laio_fin()</tt>も新設し<tt class="docutils literal">io_destroy(2)</tt>に対応します。</p>
</div></div>]]>
        <![CDATA[<div><div class="section" id="id21">
<h3>AIO完了通知</h3>
<p>シグナル、スレッド起動の通知機能に対応するため、本ライブラリ内部でスレッドを起動します。</p>
</div>
<div class="section" id="linux-aio">
<h3>Linux AIO 非同期動作条件</h3>
<p>表2に示した条件（<tt class="docutils literal">O_DIRECT</tt>、アラインメント）はそのまま受け継ぎます。このため多くの場合で、量は多くないとはいえ、アプリケーションの変更とリビルドが必要になると思われます。しかし、非同期動作にならないbufferd I/Oの場合にも、<tt class="docutils literal">io_submit(2)</tt>が対応しているので、本ライブラリも対応します。</p>
<p>前掲の<a class="reference internal" href="#id15">表1</a>で述べたように、<tt class="docutils literal">IOCB_CMD_FSYNC</tt>、<tt class="docutils literal">IOCB_CMD_FDSYNC</tt>に対応しているファイルシステムは現在存在しないため、<tt class="docutils literal">aio_fsync(3)</tt>の実装として、本ライブラリ内部で<tt class="docutils literal">io_submit</tt>（<tt class="docutils literal">IOCB_CMD_FSYNC</tt>）、<tt class="docutils literal">io_submit</tt>（<tt class="docutils literal">IOCB_CMD_FDSYNC</tt>）を発行するとエラーになってしまいます。このため、本ライブラリでは特例として扱うことにします。すなわち、<tt class="docutils literal">O_DIRECT</tt>を使用する場合、<tt class="docutils literal">aio_fsync(3)</tt>の必要性はないため、処理せず常に成功を返すことにします。<tt class="docutils literal">O_DIRECT</tt>を指定せず本ライブラリを使用する場合は省略できません。ライブラリ内部でサイズが0の<tt class="docutils literal">IOCB_CMD_PWRITE</tt>を発行し、別スレッドで<tt class="docutils literal">io_getevents(2)</tt>後に<tt class="docutils literal">fsync(2)</tt>、<tt class="docutils literal">fdatasync(2)</tt>を発行することにします。</p>
<p>また、<tt class="docutils literal">/dev/null</tt>、<tt class="docutils literal">full</tt>などもAIOに対応していません。これらに対する<tt class="docutils literal">io_submit(2)</tt>はエラーとなります。</p>
</div>
<div class="section" id="id22">
<h3>ソースレベル互換</h3>
<p>ヘッダファイルのインクルードなど、量は少なくても、多くの場合アプリケーションの変更およびリビルドが必要になると思われます。この点は、変更量を最小限に抑えるため、ライブラリヘッダファイル内でマクロによりPOSIXインタフェースシンボルを本ライブラリのものへ置き換えることで対応します。</p>
</div>
<div class="section" id="glibc-aio">
<h3>Glibc AIO 実装との非互換</h3>
<p>POSIXによれば<tt class="docutils literal">aio_cancel(3)</tt>にはパラメータ<tt class="docutils literal">struct aiocb</tt>を渡す場合とそうでない場合（NULLを渡す）があります。しかし<tt class="docutils literal">io_cancel(2)</tt>では<tt class="docutils literal">NULL</tt>はエラーとなります。すなわちLinux AIOは指定されたファイルディスクリプタに対する全AIOリクエストをキャンセルする方法を提供していません。本ライブラリ内で対応策が考えられないこともないのですが、手間がかかるため（現時点では）本ライブラリの制限事項とします。</p>
<p>Glibcの<tt class="docutils literal">lio_listio(3)</tt>は不正なファイルディスクリプタ（<tt class="docutils literal">EBADF</tt>）などのエラーのチェックを別のI/Oスレッドで行っているので、<tt class="docutils literal">lio_listio(3)</tt>はエラーを返しません。もちろん最終的にはエラーになり、<tt class="docutils literal">aio_error(3)</tt>で<tt class="docutils literal">EBADF</tt>が返されます。一方、本ライブラリの<tt class="docutils literal">lio_listio()</tt>（<tt class="docutils literal">io_submit(2)</tt>）はAIOリクエストをキューにつなぎ、直後にI/Oを開始するためこの種のエラーを検知します（早期発見ということです）。</p>
</div>
<div class="section" id="liblaio">
<h3>liblaioの使用</h3>
<p>上記を踏まえ、試作したものが同梱ソースファイル一式に含まれる<tt class="docutils literal">lib/laio/</tt>です。前掲の6aio_signal.cを書き換え、試作したliblaioをリンクしてみます。</p>
<p>6aio_signal.cの書き換え ― 11laio_signal.c 要約</p>
<div class="highlight"><pre>    :::
<span style="color: #00A000">+#include &lt;stdlib.h&gt;</span>
    :::
<span style="color: #00A000">+#include &quot;lib/laio/laio.h&quot;</span>
    :::
<span style="color: #00A000">+#ifdef Laio</span>
<span style="color: #00A000">+#define OpenFlag O_DIRECT</span>
<span style="color: #00A000">+#else</span>
<span style="color: #00A000">+#define OpenFlag 0</span>
<span style="color: #00A000">+#endif</span>
<span style="color: #00A000">+</span>
int main(int argc, char *argv[])
{
    int err, sigfd;
<span style="color: #A00000">-   char a[BUFSIZ];</span>
    ssize_t ssz;
    struct aiocb aio = {
        .aio_offset = 0,
<span style="color: #A00000">-       .aio_buf = a,</span>
<span style="color: #A00000">-       .aio_nbytes = sizeof(a),</span>
<span style="color: #00A000">+       .aio_nbytes = BUFSIZ,</span>
        .aio_reqprio = 0,
        .aio_sigevent = {
            .sigev_notify = SIGEV_SIGNAL,
        :::
<span style="color: #A00000">-   aio.aio_fildes = open(argv[1], O_RDONLY);</span>
<span style="color: #00A000">+   aio.aio_fildes = open(argv[1], O_RDONLY | OpenFlag);</span>
    assert(aio.aio_fildes &gt;= 0);
    aio.aio_sigevent.sigev_value.sival_int = aio.aio_fildes;
<span style="color: #00A000">+   err = posix_memalign((void *)&amp;aio.aio_buf, 512, BUFSIZ);</span>
<span style="color: #00A000">+   assert(!err);</span>

    sigemptyset(&amp;mask);
    sigaddset(&amp;mask, aio.aio_sigevent.sigev_signo);
    (以下略)
</pre></div>
<p>11laio_signal.cのコンパイルと実行</p>
<div class="highlight"><pre><span style="color: #19177C">$ </span>cc -I../lib/.. - L../lib/laio 11laio_signal.c -llaio -lrt <span style="color: #BB6622; font-weight: bold">\</span>
    -o 11laio_signal
<span style="color: #19177C">$ </span>./11laio_signal /mnt/test
main: aio_read from fd 3 completed
/mnt/test, 1466 <span style="color: #008000">read</span>

<span style="color: #19177C">$ </span>ln -f 11laio_signal.c 11 laio_signal_dio.c
<span style="color: #19177C">$ </span>cc -I ../lib/.. -DLaio -L ../lib/ laio11laio_signal_dio.c -llaio -lrt <span style="color: #BB6622; font-weight: bold">\</span>
    -o 11laio_signal_dio
<span style="color: #19177C">$ </span>./11laio_signal_dio /mnt/test
main: aio_read from fd 3 completed
/mnt/test, 1466 <span style="color: #008000">read</span>
</pre></div>
<p>書き換えのポイントは<tt class="docutils literal">laio.h</tt>のインクルードです。このヘッダファイルが<tt class="docutils literal">aio_read(3)</tt>などを<tt class="docutils literal">laio_read()</tt>へ変換するため、書き換え量は極小で済みます。ただし、動作は非同期にはなりません。</p>
<p>非同期動作にするためには<tt class="docutils literal">O_DIRECT</tt>とアラインメントを加える必要があります。11laio_signal.cではコンパイル時に-DLaioを加えると非同期になるようにしました。同様の方法はliblaioに添付のテストプログラムの<tt class="docutils literal">laiotest.h</tt>でも用いています。</p>
</div>
<div class="section" id="id23">
<h3>簡単に性能比較</h3>
<p>本ライブラリの実行性能をGlibcと比較してみましたが、Direct I/Oを用いると、やはり速度面では不利でした。通常のbuffered I/Oではそれほど差は見られませんでした。</p>
<p>有意な差が見えたのは<tt class="docutils literal">lio_listio(3)</tt>です。POSIX AIOの中では、いやシステムコール、ライブラリ一般から言っても、<tt class="docutils literal">lio_listio(3)</tt>は非常に強力です。一度に多数のI/Oを発行する機能は実装も悩ましいところです。Glibcの実装では指定された分だけスレッドからI/Oする、複数の<tt class="docutils literal">aio_read(3)</tt>のように処理するという比較的単純なアプローチですが、Linux AIOの<tt class="docutils literal">io_submit(2)</tt>ではネイティブに複数I/Oに対応しているため、本ライブラリの<tt class="docutils literal">lio_listio(3)</tt>はこの機能を活用しています。少数のAIOを発行する場合は、Glibcの複数スレッドアプローチでも、スレッド起動は通常充分に軽いため、満足な性能が得られるでしょう。しかし、大量になると単発の<tt class="docutils literal">aio_read(3)</tt>を複数繰り返すよりも<tt class="docutils literal">lio_listio(3)</tt>が威力を発揮します。この場合の性能はGlibcの実装では不満を感じるかもしれません。libaioや本記事で試作したliblaioの有効性が活きる場面です。liblaio付属テストプログラムでも<tt class="docutils literal">lio_listio(3)</tt>の性能は、大きくとは言えませんが、Glibcよりも常に良好な結果を示しました。ただし、
この簡単なテストは同じファイルに 対する読み取りを一度に多数発行しただけで、差が見えにくい内容です。大量に発行して、やっとこれだけの差が見えたという程度の結果で、性能測定としては信頼に足るものではありません。</p>
<p>テストプログラムaio3_lioの実行結果部分だけ抜粋します。aio3_lioがGlibcを、nonlaio3_lioが<tt class="docutils literal">O_DIRECT</tt>を指定せず本ライブラリを、laio3_lioが<tt class="docutils literal">O_DIRECT</tt>を指定して本ライブラリを、それぞれ用いた場合です。</p>
<p>liblaio付属テストプログラム実行結果（抜粋）</p>
<div class="highlight"><pre><span style="color: #19177C">$ </span>make -s <span style="color: #008000">test</span>
aio2_fsync + <span style="color: #008000">suspend</span>
    :::
aio3_lio
2.65user 0.06system 0:03.42elapsed 79%CPU <span style="color: #666666">(</span>0 avgtext+0 avgdata 0maxresident<span style="color: #666666">)</span>k
0inputs+0outputs <span style="color: #666666">(</span>0major+31412minor<span style="color: #666666">)</span>pagefaults 0swaps
nonlaio3_lio
0.16user 1.68system 0:02.68elapsed 68%CPU <span style="color: #666666">(</span>0avgtext+0 avgdata 0maxresident<span style="color: #666666">)</span>k
0inputs+0outputs <span style="color: #666666">(</span>0major+41636minor<span style="color: #666666">)</span>pagefaults 0swaps
laio3_lio
0.17user 4.25system 0:05.57elapsed 79%CPU <span style="color: #666666">(</span>0avgtext+0avgdata 0maxresident<span style="color: #666666">)</span>k
4915200inputs+0outputs <span style="color: #666666">(</span>0major+41718minor<span style="color: #666666">)</span>pagefaults 0swaps
aio1_read + cancel
    :::
</pre></div>
<p>本記事ではファイルI/O全般からAIOまで解説し、さらにAIOライブラリを試作してみました。ライブラリには制限事項も複数残っており、まだ試用レベルですが、将来発展すればpthreadのようにNATL（Native POSIX AIO Library）と名乗る日が来るかもしれません。</p>
<p>もちろん興味を失えばこれで終わりになってしまうかもしれません　:-)</p>
<p>（初出: H22/4）</p>
<p>Copyright © 2010 SENJU Jiro</p>
<p class="footnote">本記事のサンプルコードは、以下のリンクよりダウンロードすることができます。記事と合わせてご参照ください。<br />
[<a href="http://www.oreilly.co.jp/pub/pg_high/ph20100730.tar.bz2">サンプルコード</a>]<br />
また、本稿で取り上げた内容は、記事の執筆時の情報に基づいており、その後の実装の進展、変更等により、現在の情報とは異なる場合があります。<br />
本連載「プログラマーズ・ハイ」では読者からのご意見、ご感想を切望しています。今後取り上げるテーマなどについてもご意見をお寄せください。</p>
</div>
</div>]]>
    </content>
</entry>

<entry>
    <title>バッファキャッシュとAIO（3）</title>
    <link rel="alternate" type="text/html" href="http://www.oreilly.co.jp/community/blog/2010/09/buffer-cache-and-aio-part3.html" />
    <id>tag:www.oreilly.co.jp,2010:/community/blog//4.661</id>

    <published>2010-09-20T07:00:00Z</published>
    <updated>2011-06-30T00:12:25Z</updated>

    <summary> 前回までファイル I/O 全般について簡単に振り返りました。いよいよ本題のAIOに取り掛かります。今回は、POSIXのAIOインタフェースと、LinuxカーネルのAIOサポートについて紹介します。 ...</summary>
    <author>
        <name>千住治郎</name>
        
    </author>
    
        <category term="Programmer&apos;s High" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="ja" xml:base="http://www.oreilly.co.jp/community/blog/">
        <![CDATA[<img alt="http://www.oreilly.co.jp/community/blog/images/union/pg_high_logo.png" src="http://www.oreilly.co.jp/community/blog/images/union/pg_high_logo.png" />
<p class="lead">前回までファイル I/O 全般について簡単に振り返りました。いよいよ本題のAIOに取り掛かります。今回は、POSIXのAIOインタフェースと、LinuxカーネルのAIOサポートについて紹介します。</p>
<h2>POSIX AIO インタフェース</h2>
<p>バッファキャッシュにより緩和されるとはいえ、ファイル I/Oの最終到達地点はディスクですから、同期的なI/Oはやはりその時間が問題視されることがあります。まだバッファキャッシュに存在しないデータを読み取る場合には遅いディスク必ず待たなければなりません。この動作を非同期に行い、待っている間に他の処理を進められるようにするのが非同期 I/O、AIO（Asynchoronous I/O）です。POSIXでは<tt class="docutils literal">aio_read(3)</tt>、<tt class="docutils literal">aio_write(3)</tt>、<tt class="docutils literal">aio_suspend(3)</tt>、<tt class="docutils literal">aio_fsync(3)</tt>、<tt class="docutils literal">aio_return(3)</tt>、<tt class="docutils literal">aio_cancel(3)</tt>、<tt class="docutils literal">aio_error(3)</tt>、<tt class="docutils literal">lio_listio(3)</tt>を定義しています。例えば<tt class="docutils literal">read(2)</tt>に対応するAIOインタフェースは次のように定義されています。</p>
<p>aio_read(3)</p>
<div class="highlight"><pre><span style="color: #BC7A00"># include &lt;aio.h&gt;</span>

<span style="color: #B00040">int</span> aio_read(<span style="color: #008000; font-weight: bold">struct</span> aiocb <span style="color: #666666">*</span>);

<span style="color: #008000; font-weight: bold">struct</span> aiocb {
    <span style="color: #B00040">int</span>             aio_fildes;     <span style="color: #408080; font-style: italic">/*ファイルディスクリプタ */</span>
    <span style="color: #B00040">off_t</span>           aio_offset;     <span style="color: #408080; font-style: italic">/* オフセット */</span>
    <span style="color: #008000; font-weight: bold">volatile</span> <span style="color: #B00040">void</span>   <span style="color: #666666">*</span>aio_buf;       <span style="color: #408080; font-style: italic">/* バッファ */</span>
    <span style="color: #B00040">size_t</span>          aio_nbytes;     <span style="color: #408080; font-style: italic">/* バイト数 */</span>
    <span style="color: #B00040">int</span>             aio_reqprio;    <span style="color: #408080; font-style: italic">/* 相対優先度 */</span>
    <span style="color: #008000; font-weight: bold">struct</span> sigevent aio_sigevent;   <span style="color: #408080; font-style: italic">/* 通知方法 後述 */</span>
    <span style="color: #B00040">int</span>             aio_lio_opcode; <span style="color: #408080; font-style: italic">/* I/O種類 */</span>
};

<span style="color: #BC7A00"># include &lt;signal.h&gt;</span>
<span style="color: #008000; font-weight: bold">struct</span> sigevent {
    <span style="color: #B00040">int</span>             sigev_notify;      <span style="color: #408080; font-style: italic">/* 通知方法 */</span>
    <span style="color: #B00040">int</span>             sigev_signo;       <span style="color: #408080; font-style: italic">/* シグナル番号 */</span>
    <span style="color: #008000; font-weight: bold">union</span> sigval    sigev_value;       <span style="color: #408080; font-style: italic">/* ユーザデータ */</span>
    <span style="color: #B00040">void</span>(<span style="color: #666666">*</span>sigev_notify_function)(<span style="color: #008000; font-weight: bold">union</span> sigval);   <span style="color: #408080; font-style: italic">/* スレッド関数 */</span>
    pthread_attr_t  <span style="color: #666666">*</span>sigev_notify_attributes;     <span style="color: #408080; font-style: italic">/* スレッド属性 */</span>
};
</pre></div>]]>
        <![CDATA[<p>通常の<tt class="docutils literal">read(2)</tt>の3つのパラメータは、<tt class="docutils literal">struct aiocb</tt>のメンバ<tt class="docutils literal">aio_fildes</tt>、<tt class="docutils literal">aio_buf</tt>、<tt class="docutils literal">aio_nbytes</tt>にそれぞれ対応します。非同期という性質のため、I/O時にファイルポジションが維持されていることは期待できず、<tt class="docutils literal">pread(2)</tt>同様に<tt class="docutils literal">aio_offset</tt>も指定します。<tt class="docutils literal">aio_reqprio</tt>は内部で非同期に実行されるI/Oコンテキストの優先度を決定する際に使用されます。<tt class="docutils literal">aio_lio_opcode</tt>は<tt class="docutils literal">lio_listio(3)</tt>以外では使用しません。<tt class="docutils literal">read(2)</tt>と<tt class="docutils literal">aio_read(3)</tt>の関係が分かれば、<tt class="docutils literal">aio_write(3)</tt>、<tt class="docutils literal">aio_fsync(3)</tt>については説明は不要でしょう。 <tt class="docutils literal"><span class="pre">:-)</span></tt>AIOの完了を待ち合わせるのが<tt class="docutils literal">aio_suspend(3)</tt>です。まず<tt class="docutils literal">aio_read(3)</tt>をコールし、ファイルを読み取っている際中に別の処理を進め、読み取ったファイル内容を参照する際に<tt class="docutils literal">aio_suspend(3)</tt>をコールします。もちろん、すでにAIOが完了していることが分かっていれば<tt class="docutils literal">aio_suspend(3)</tt>は省略できます。待ち合わせずに完了しているかだけを確認するには<tt class="docutils literal">aio_error(3)</tt>を用います。戻り値が<tt class="docutils literal">EINPROGRESS</tt>ならばまだAIO未完了、それ以外ならば、正常終了、異常終了にかかわらず、AIOは完了しています。正常終了の場合には戻り値、<tt class="docutils literal">aio_read(3)</tt>ならば読み取ったバイト数を返すのが<tt class="docutils literal">aio_return(3)</tt>です。この値はAIO完了後でなければ意味を持ちません。</p>
<p>発行済みAIOをキャンセルするのは<tt class="docutils literal">aio_cancel(3)</tt>です。もちろんすでにAIOが完了していればエラーとなります。</p>
<p><tt class="docutils literal">lio_listio(3)</tt>は複数のAIOを一度に発行できる強力なシステムコールです。<tt class="docutils literal">aio_lio_opcode</tt>にはL<tt class="docutils literal">IO_READ</tt>、<tt class="docutils literal">LIO_WRITE</tt>、<tt class="docutils literal">LIO_NOP</tt>を指定でき、一度<tt class="docutils literal">struct aiocb</tt>の配列を用意すれば<tt class="docutils literal">aio_lio_opcode</tt>を操作するだけで済み、配列全体の再作成は省略できます。多数のファイルディスクリプタを繰り返し処理する場合には、効率が期待できます（しかし、後述するようにGlibcの実装ではスレッドを多用する点に注意が必要です）。またすべての処理完了を待つか否かも指定できます（<tt class="docutils literal">LIO_WAIT</tt>、<tt class="docutils literal">LIO_NOWAIT</tt>）。</p>
<p><tt class="docutils literal">lio_listio(3)</tt>も大きな特徴ですが、最大の特徴は<tt class="docutils literal">struct aiocb</tt>のメンバ<tt class="docutils literal">aio_sigevent</tt>です。非同期に実行したI/Oの完了通知を受け取る方法を指定するもので、<tt class="docutils literal">sigev_notify</tt>にシグナルによる通知（<tt class="docutils literal">SIGEV_SIGNAL</tt>）、スレッド起動による通知（<tt class="docutils literal">SIGEV_THREAD</tt>）のどちらからを指定します。通常はあまり使用しないかもしれませんが、通知なし（<tt class="docutils literal">SIGEV_NONE</tt>）も定義されています。サンプルコードを2つ挙げます。</p>
<p>スレッド起動によるAIOの完了通知 ― 5aio_thread.c 要約</p>
<div class="highlight"><pre><span style="color: #008000; font-weight: bold">struct</span> {
    pthread_cond_t cond;
    pthread_mutex_t mtx;
    <span style="color: #B00040">int</span> flag;
} notified <span style="color: #666666">=</span> {
    .cond   <span style="color: #666666">=</span> PTHREAD_COND_INITIALIZER,
    .mtx    <span style="color: #666666">=</span> PTHREAD_MUTEX_INITIALIZER,
    .flag   <span style="color: #666666">=</span> <span style="color: #666666">0</span>
};

<span style="color: #B00040">void</span> <span style="color: #0000FF">thread_func</span>(<span style="color: #008000; font-weight: bold">union</span> sigval sv)
{
    printf(<span style="color: #BA2121">&quot;%s : aio_read from fd %d completed </span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">&quot;</span>,
        __func__, sv.sival_int);

    pthread_mutex_lock(<span style="color: #666666">&amp;</span>notified.mtx);
    notified.flag <span style="color: #666666">=</span> <span style="color: #666666">1</span>;
    pthread_cond_signal(<span style="color: #666666">&amp;</span> notified.cond);
    pthread_mutex_unlock(<span style="color: #666666">&amp;</span> notified.mtx);
}

main ()
{
    <span style="color: #B00040">char</span> a[BUFSIZ];
    <span style="color: #008000; font-weight: bold">struct</span> aiocb aio <span style="color: #666666">=</span> {
        .aio_offset   <span style="color: #666666">=</span> <span style="color: #666666">0</span>,
        .aio_buf      <span style="color: #666666">=</span> a,
        .aio_nbytes   <span style="color: #666666">=</span> <span style="color: #008000; font-weight: bold">sizeof</span>(a),
        .aio_reqprio  <span style="color: #666666">=</span> <span style="color: #666666">0</span>,
        .aio_sigevent <span style="color: #666666">=</span> {
            .sigev_notify            <span style="color: #666666">=</span> SIGEV_THREAD,
            .sigev_notify_function   <span style="color: #666666">=</span> thread_func,
            .sigev_notify_attributes <span style="color: #666666">=</span> <span style="color: #008000">NULL</span>
        }
    };

    aio.aio_fildes <span style="color: #666666">=</span> open(argv[<span style="color: #666666">1</span>], O_RDONLY);
    aio.aio_sigevent.sigev_value.sival_int <span style="color: #666666">=</span> aio.aio_fildes;
    aio_read(<span style="color: #666666">&amp;</span>aio);

    <span style="color: #408080; font-style: italic">/* do other jobs */</span>

    pthread_mutex_lock(<span style="color: #666666">&amp;</span>notified.mtx);
    <span style="color: #008000; font-weight: bold">while</span> (<span style="color: #666666">!</span>notified.flag)
        pthread_cond_wait(<span style="color: #666666">&amp;</span>notified.cond, <span style="color: #666666">&amp;</span>notified.mtx);
    pthread_mutex_unlock(<span style="color: #666666">&amp;</span>notified.mtx);
}
</pre></div>
<p>シグナルによるAIOの完了通知 ― 6aio_signal.c 要約</p>
<div class="highlight"><pre>main ()
{
    <span style="color: #B00040">char</span> a[BUFSIZ];
    <span style="color: #008000; font-weight: bold">struct</span> aiocb aio <span style="color: #666666">=</span> {
        .aio_offset  <span style="color: #666666">=</span> <span style="color: #666666">0</span>,
        .aio_buf     <span style="color: #666666">=</span> a,
        .aio_nbytes  <span style="color: #666666">=</span> <span style="color: #008000; font-weight: bold">sizeof</span>(a),
        .aio_reqprio <span style="color: #666666">=</span> <span style="color: #666666">0</span>,
        .aio_sigevent <span style="color: #666666">=</span> {
            .sigev_notify <span style="color: #666666">=</span> SIGEV_SIGNAL,
            .sigev_signo  <span style="color: #666666">=</span> SIGRTMIN <span style="color: #666666">+</span> <span style="color: #666666">1</span>
        }
    };
    sigset_t mask;
    <span style="color: #008000; font-weight: bold">struct</span> signalfd_siginfo ssi;

    aio.aio_fildes <span style="color: #666666">=</span> open(argv[<span style="color: #666666">1</span>], O_RDONLY);
    aio.aio_sigevent.sigev_value.sival_int <span style="color: #666666">=</span> aio.aio_fildes;

    sigemptyset(<span style="color: #666666">&amp;</span>mask);
    sigaddset(<span style="color: #666666">&amp;</span>mask, aio.aio_sigevent.sigev_signo);
    sigprocmask(SIG_BLOCK, <span style="color: #666666">&amp;</span>mask, <span style="color: #008000">NULL</span>);
    sigfd <span style="color: #666666">=</span> signalfd(<span style="color: #666666">-1</span>, <span style="color: #666666">&amp;</span>mask, <span style="color: #408080; font-style: italic">/* flags unused */</span><span style="color: #666666">0</span>);

    aio_read(<span style="color: #666666">&amp;</span>aio);

    <span style="color: #408080; font-style: italic">/* do other jobs */</span>

    read(sigfd, <span style="color: #666666">&amp;</span>ssi, <span style="color: #008000; font-weight: bold">sizeof</span>(ssi));
    printf(<span style="color: #BA2121">&quot;%s: aio_read from fd %d completed</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">&quot;</span>,
        __func__, ssi.ssi_int);
}
</pre></div>
<p>5aio_thread.c、6aio_signal.cいずれもAIOの通知方法を示すためだけのサンプルで、動作に非同期性を必要とするものではありません。本来は<tt class="docutils literal">/* do other jobs */</tt>というコメント部分に並列に実行する処理を記述します。</p>
<p class="footnote">本筋から逸れますが、上記二サンプルでは起動されるスレッド、および受信したシグナルへユーザデータを渡す方法も実装しています。大したデータではないので、あくまでも例です。<tt class="docutils literal">aio.aio_sigevent.sigev_value</tt>がそれですが、整数を渡す場合には<tt class="docutils literal">sival_int</tt>を、それ以外の場合には<tt class="docutils literal">void sival_ptr</tt>を用います。</p>
<p class="footnote"><tt class="docutils literal">SIGEV_THREAD</tt>の場合では、スレッド引数は<tt class="docutils literal">union sigval</tt>となりますが、これが値を代入した<tt class="docutils literal">sigev_value</tt>です。<tt class="docutils literal">SIGEV_SIGNAL</tt>では、受信した<tt class="docutils literal">siginfo_t</tt>の<tt class="docutils literal">si_value</tt>が<tt class="docutils literal">sigev_value</tt>に相当し、<tt class="docutils literal">si_int</tt>、<tt class="docutils literal">si_ptr</tt>マクロを用い参照します。</p>
</div>
<div class="section" id="glibc-rt-aio">
<h2>Glibc(RT)のAIO実装</h2>
<p>GNU Libc（厳密には同梱の<tt class="docutils literal">librt</tt>）はすでにPOSIX AIOインタフェースを実装しています。そのv2.7での<tt class="docutils literal">aio_read(3)</tt>の動作を要約してみます。</p>
<ul class="simple">
<li>内部キューのメモリ確保</li>
<li><tt class="docutils literal">pthread_create(3)</tt>をコールし、内部スレッドを起動</li>
<li>AIOリクエストをキューへつなぐ</li>
</ul>
<p>内部キューへAIO リクエストをつなげば<tt class="docutils literal">aio_read(3)</tt>はリターンします。内部スレッドは次のように動作します。</p>
<ul class="simple">
<li>キューからAIOリクエストを取り出す</li>
<li>内容に応じ<tt class="docutils literal">pread(2)</tt>などを発行</li>
<li>内容に応じ通知</li>
<li>上記処理の繰り返し</li>
</ul>
<p>内部スレッドがキューからAIOリクエストを取り出そうとした時にもうリクエストが一つも存在していなければ、内部スレッドは短時間の眠りに着きます。起床してもまだAIOリクエストがなければ終了します。これはスレッドを終了／再作成／起動するコストを節約するため、今は用がなくても近い将来出番が来るかもしれないので、少しの間は待つという動作です。</p>
<p>逆に起床したら複数のAIOリクエストが溜っていた場合には、自分一人では手が足りないため、内部スレッドが別の内部スレッドを起動します。新たに起動された内部スレッドも同じように動作します。</p>
<p>POSIXでは定義されていませんが、Glibcでは<tt class="docutils literal">struct aioinit</tt>、<tt class="docutils literal">aio_init(3)</tt>を定義し、内部動作も制御可能としています。例えば内部スレッド数、キューに保持可能なAIOリクエスト数の上限を設定可能です。</p>
<p>この実装で注目すべき点は、AIOが多用されることにより、内部では複数回<tt class="docutils literal">pthread_create(3)</tt>がコールされ、複数の内部スレッドが起動される点でしょう。上限を設定可能とはいえ（デフォルトでは20）、スレッド数を不用意に多くしてしまうと性能劣化を招く恐れがあります。</p>
</div>
<div class="section" id="linuxaio">
<h2>LinuxカーネルのAIO対応</h2>
<p>LinuxはカーネルレベルでAIOをサポートしています。すなわち上述のGlibcによる ユーザ空間でのスレッドによる実装ではありません。Glibcの実装ではライブラリ内部で<tt class="docutils literal">pread(2)</tt>など対応するシステムコールを発行しますが、カーネルレベルのAIOサポートでは<tt class="docutils literal">io_submit(2)</tt>一つで全種類のI/Oに対応します。</p>
<p>LinuxのAIO システムコール</p>
<div class="highlight"><pre><span style="color: #BC7A00"># include &lt;linux/aio_abi.h&gt;</span>

<span style="color: #B00040">long</span> io_setup(<span style="color: #B00040">unsigned</span> nr_events, aio_context_t <span style="color: #666666">*</span>ctxp);
<span style="color: #B00040">long</span> io_submit(aio_context_t ctx_id, <span style="color: #B00040">long</span> nr, <span style="color: #008000; font-weight: bold">struct</span> iocb <span style="color: #666666">**</span>iocbpp);
<span style="color: #B00040">long</span> io_getevents(aio_context_t ctx_id, <span style="color: #B00040">long</span> min_nr, <span style="color: #B00040">long</span> nr,
    <span style="color: #008000; font-weight: bold">struct</span> io_event <span style="color: #666666">*</span>events, <span style="color: #008000; font-weight: bold">struct</span> timespec <span style="color: #666666">*</span>timeout);
<span style="color: #B00040">long</span> io_destroy(aio_context_t ctx);
<span style="color: #B00040">long</span> io_cancel(aio_context_t ctx_id, <span style="color: #008000; font-weight: bold">struct</span> iocb <span style="color: #666666">*</span>iocb,
    <span style="color: #008000; font-weight: bold">struct</span> io_event <span style="color: #666666">*</span>result);

<span style="color: #008000; font-weight: bold">struct</span> iocb {
    <span style="color: #408080; font-style: italic">/* アプリケーションが使用しないメンバについては省略 */</span>

    __u64aio_data; <span style="color: #408080; font-style: italic">/* io_eventのdataへ代入される */</span>

    __u16 aio_lio_opcode; <span style="color: #408080; font-style: italic">/* IOCB_CMD_を代入する */</span>
    __s16 aio_reqprio;    <span style="color: #408080; font-style: italic">/* 未使用 */</span>
    __u32 aio_fildes;
    __u64 aio_buf;
    __u64 aio_nbytes;
    __s64 aio_offset;

    __u32 aio_flags;      <span style="color: #408080; font-style: italic">/* aio_resfdを使用するか否か */</span>
    __u32 aio_resfd;      <span style="color: #408080; font-style: italic">/* eventfd (使用する場合) */</span>
};

<span style="color: #008000; font-weight: bold">struct</span> io_event {
    __u64 data;           <span style="color: #408080; font-style: italic">/* iocbのaio_dataが代入される */</span>
    __u64 obj;            <span style="color: #408080; font-style: italic">/* iocbポインタ */</span>
    __s64 res;            <span style="color: #408080; font-style: italic">/* I/Oの戻り値 */</span>
    __s64 res2;           <span style="color: #408080; font-style: italic">/* gadgetfsしか使用していない */</span>
};
</pre></div>
<dl class="docutils">
<dt><tt class="docutils literal">io_setup(2)</tt></dt>
<dd>パラメータのnr_events 個の非同期 I/Oに対応したメモリ領域をシステム内に確保し、<tt class="docutils literal">aio_context_t</tt>ディスクリプタ（ハンドル）として<tt class="docutils literal">ctxp</tt>に返します。渡す<tt class="docutils literal">ctxp</tt>は事前に0で初期化しておきます。また、システムワイドの非同期I/O数の上限は<tt class="docutils literal"><span class="pre">/proc/sys/fs/aio-max-nr</span></tt>から設定／参照可能です。</dd>
<dt><tt class="docutils literal">io_submit(2)</tt></dt>
<dd><p class="first">AIOリクエストをシステム内部のキューへつなぎます。AIO発行の本体に相当します。厳密に言うと、キューへつないだ直後にI/Oが開始されますが、この動作は非同期性を損なう恐れがあります。LinuxがAIOを実装した当初は動作は違ったと思うのですが。また、このため、異常なファイルディスクリプタ（<tt class="docutils literal">EBADF</tt>）などのエラーは<tt class="docutils literal">io_submit(2)</tt>が検知します。後述するように、この点には注意が必要となる場面があり得ます。</p>
<p class="last"><tt class="docutils literal">struct iocb</tt>はAIOリクエストごとに一つづつ必要で、I/O完了前に再利用はできません。このパラメータが構造体配列ではなく構造体ポインタ配列である点も一つの特徴と言えます。リクエストの内容、構造体メンバは前述のPOSIX AIOと同様ですが、<tt class="docutils literal">aio_reqprio</tt>は使用されません。GlibcのAIOでは、おかしな値が代入されていた場合はエラーとされますが、<tt class="docutils literal">io_submit(2)</tt>では無視します。また<tt class="docutils literal">aio_lio_opcode</tt>には次の値が指定可能です（<a class="reference internal" href="#id15">表1</a>）。</p>
</dd>
</dl>
<p><span class="target" id="id15">表1</span> <tt class="docutils literal">io_submit(2)</tt>のI/O種類</p>
<table border="1" class="docutils">
<colgroup>
<col width="51%" />
<col width="49%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head">iocb.aio_lio_opcode</th>
<th class="head">相当システムコール</th>
</tr>
</thead>
<tbody valign="top">
<tr><td>IOCB_CMD_PREAD</td>
<td>pread(2)</td>
</tr>
<tr><td>IOCB_CMD_PWRITE</td>
<td>pwrite(2)</td>
</tr>
<tr><td>IOCB_CMD_FSYNC</td>
<td>fsync(2)</td>
</tr>
<tr><td>IOCB_CMD_FDSYNC</td>
<td>fdatasync(2)</td>
</tr>
<tr><td>IOCB_CMD_PREADV</td>
<td>preadv(2)</td>
</tr>
<tr><td>IOCB_CMD_PWRITEV</td>
<td>pwritev(2)</td>
</tr>
</tbody>
</table>
<p class="footnote">現在<tt class="docutils literal">IOCB_CMD_FSYNC</tt>、<tt class="docutils literal">IOCB_CMD_FDSYNC</tt>に対応しているファイルシステムは存在しないため、指定してもエラーになる。</p>
<p><tt class="docutils literal">aio_flags</tt>、<tt class="docutils literal">aio_resfd</tt>については後述します。非同期動作はDirect I/Oの場合にのみ有効となり、<tt class="docutils literal">O_DIRECT</tt>を指定しないファイルディスクリプタに対する<tt class="docutils literal">io_submit(2)</tt>は非同期にもエラーにもならず、通常のI/Oとして処理されます（<a class="reference internal" href="#id16">表2</a>）。Direct I/Oの場合の動作については3節で述べました。ただし、<tt class="docutils literal">io_submit(2)</tt>、または<tt class="docutils literal">O_DIRECT</tt>に対応していないものも存在し（パイプや<tt class="docutils literal">/dev/null</tt>など）、これらに対する<tt class="docutils literal">io_submit(2)</tt>はエラーとなります。</p>
<p><span class="target" id="id16">表2</span> Linux AIOの非同期動作条件</p>
<table border="1" class="docutils">
<colgroup>
<col width="32%" />
<col width="18%" />
<col width="21%" />
<col width="29%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head"><ul class="first last simple">
<li></li>
</ul>
</th>
<th class="head"><ul class="first last simple">
<li></li>
</ul>
</th>
<th class="head" colspan="2">アラインメント</th>
</tr>
<tr><th class="head"><ul class="first last simple">
<li></li>
</ul>
</th>
<th class="head"><ul class="first last simple">
<li></li>
</ul>
</th>
<th class="head">なし</th>
<th class="head">あり</th>
</tr>
</thead>
<tbody valign="top">
<tr><td>O_DIRECT</td>
<td>なし</td>
<td>同期</td>
<td>同期</td>
</tr>
<tr><td><ul class="first last simple">
<li></li>
</ul>
</td>
<td>あり</td>
<td>エラー</td>
<td>非同期</td>
</tr>
</tbody>
</table>
<p>言い方を換えると、<tt class="docutils literal">io_submit(2)</tt>はAIO専用ではなく、<tt class="docutils literal">O_DIRECT</tt>を指定しない同期的ファイルI/Oの場合にも使用でき、大量のI/Oを一度に発行できるという強力なシステムコールです。</p>
<dl class="docutils">
<dt><tt class="docutils literal">io_getevents(2)</tt></dt>
<dd>処理を完了したAIO リクエストの情報を読み取ります。POSIX AIOとは異なりシグナルなどの通知はなく、アプリケーションが<tt class="docutils literal">io_getevents(2)</tt>を発行するか、または後述するeventfdを用い、AIO完了を自ら問い合わせます。</dd>
<dt><tt class="docutils literal">io_destroy(2)</tt></dt>
<dd>渡された<tt class="docutils literal">aio_context_t</tt>ハンドルに対応するシステム資源を解放します。未実行のAIOリクエストがキュー内にあればキャンセルされます。</dd>
<dt><tt class="docutils literal">io_cancel(2)</tt></dt>
<dd><tt class="docutils literal">io_submit(2)</tt>に渡したAIOリクエストをキャンセルします。キャンセルできた場合は成功（0）を、できなかった場合はエラー（<tt class="docutils literal">EAGAIN</tt>）を、それぞれ返します。Linux AIOのサンプルコードを挙げます。</dd>
</dl>
<p>Linux AIOによるファイル読み取り ― 7raw_laio.c要約</p>
<div class="highlight"><pre>main ()
{
    aio_context_t laio;
    <span style="color: #008000; font-weight: bold">struct</span> io_event ev;
    <span style="color: #008000; font-weight: bold">struct</span> iocb <span style="color: #666666">*</span>pcb, laiocb <span style="color: #666666">=</span> {
        .aio_lio_opcode <span style="color: #666666">=</span> IOCB_CMD_PREAD,
        .aio_nbytes <span style="color: #666666">=</span> BUFSIZ,
        .aio_offset <span style="color: #666666">=</span> <span style="color: #666666">0</span>
    };

    laiocb.aio_fildes <span style="color: #666666">=</span> open(argv[<span style="color: #666666">1</span>], O_RDONLY <span style="color: #666666">|</span> O_DIRECT);
    laiocb.aio_data <span style="color: #666666">=</span> laiocb.aio_fildes;
    posix_memalign((<span style="color: #B00040">void</span> <span style="color: #666666">*</span>)<span style="color: #666666">&amp;</span>laiocb.aio_buf, <span style="color: #666666">512</span>, BUFSIZ);

    memset(<span style="color: #666666">&amp;</span>laio, <span style="color: #666666">0</span>, <span style="color: #008000; font-weight: bold">sizeof</span>(laio));
    io_setup(<span style="color: #666666">1</span>, <span style="color: #666666">&amp;</span>laio);
    pcb <span style="color: #666666">=</span> <span style="color: #666666">&amp;</span>laiocb;
    io_submit(laio, <span style="color: #666666">1</span>, <span style="color: #666666">&amp;</span>pcb);

    <span style="color: #408080; font-style: italic">/* do other jobs */</span>

    <span style="color: #408080; font-style: italic">// err = io_getevents(laio, 0, 1, &amp;ev, NULL);</span>
    err <span style="color: #666666">=</span> io_getevents(laio, <span style="color: #666666">1</span>, <span style="color: #666666">1</span>, <span style="color: #666666">&amp;</span>ev, <span style="color: #008000">NULL</span>);

    printf(<span style="color: #BA2121">&quot;%s: io_submit from fd %llu completed</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">&quot;</span>,
        __func__, ev.data);
    printf(<span style="color: #BA2121">&quot;%s, %lld read</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">&quot;</span>, argv[<span style="color: #666666">1</span>], ev.res);
}
</pre></div>
<p class="foornote">GlibcはLinux AIOシステムコールに対応していませんので、前述の<tt class="docutils literal">io_submit(2)</tt>などのシステムコールを発行するには、システムコールラッパーを別途定義する必要があり、7raw_laio.cでは<tt class="docutils literal">syscall(2)</tt>を用いています。</p>
<p>LinuxカーネルレベルAIOはPOSIX AIOとは異なり、シグナルまたはスレッド起動による通知機能はありません。7raw_laio.cでは<tt class="docutils literal">io_getevents(2)</tt>の<tt class="docutils literal">min_nr</tt>に1を渡しているため、ここでAIO完了を待ち合わせる動作になります。<tt class="docutils literal">min_nr</tt>を0にすると、まだ完了していなくても待ち合わせません。</p>
<p><tt class="docutils literal">min_nr</tt>を変更すればブロックする／しないを制御できるので、これだけでも用は足りると思われますが、Linux AIOでは<tt class="docutils literal">eventfd(2)</tt>を経由する方法も提供されています。7raw_laio.cに<tt class="docutils literal">eventfd(2)</tt>を追加した8raw_laio_ev.cもサンプルコード一式に含めておきます。</p>
<p class="foornote">Glibcはすでに<tt class="docutils literal">eventfd(2)</tt>に対応しており、<tt class="docutils literal">eventfd_read(3)</tt>、<tt class="docutils literal">eventfd_write(3)</tt>なども追加されていますが、定義/実装されたのがv2.7から、しかしヘッダファイルが提供された（インストールされるようになった）のはv2.8からという経緯があります。実は筆者はGlibc v2.7 環境（えぇ、古典派と呼ばれようがDebian stable（lenny）を使っていますとも）なので、ちと不便があります。v2.7では<tt class="docutils literal">eventfd(2)</tt> を使おうと<tt class="docutils literal">sys/eventfd.h</tt>をincludeしようとしても存在しないためコンパイルエラーになりました。一時しのぎですが8raw_laio_ev.cではGlibc v2.7のソースから<tt class="docutils literal">sys/eventfd.h</tt>の一部をコピーすることでこの問題を回避しています。</p>
<p>Glibcの<tt class="docutils literal">aio_read(3)</tt>の動作を要約したように、Linux AIOの動作も要約してみます。<tt class="docutils literal">io_submit(2)</tt>は複数のAIOリクエストを受け取れます。その一つづつに対し次のように処理します。</p>
<ul class="simple">
<li>AIOリクエストのメモリ領域を確保</li>
<li>ファイルシステムへAIOリクエストを渡す</li>
<li><tt class="docutils literal">O_DIRECT</tt>が指定されていない、またはサイズを拡張する書き込みの場合は完了を待つ。すなわち非同期動作にならない</li>
<li>それ以外の場合は完了を待たず非同期動作になる</li>
</ul>
<p>ここで処理の流れはひとまず終了し、アプリケーションへ制御が返り、処理を進められます。以降の動作は基本的に前述のDirect I/Oと同じです。ファイルシステムの下位に位置するブロックデバイスがAIOリクエストを処理し、その後ブロックデバイスからI/O完了の通知を受けると、AIOはシステム内部のリングバッファ内に完了情報を蓄えます。</p>
<p>ここで前述の<tt class="docutils literal">aio_flags</tt>、<tt class="docutils literal">aio_resfd</tt>が関係する動作になります。<tt class="docutils literal">aio_resfd</tt>にeventfdを、<tt class="docutils literal">aio_flags</tt>に<tt class="docutils literal">IOCB_FLAG_RESFD</tt>をそれぞれ指定すると、そのeventfdに完了通知が届きます。eventfdへの完了通知は<tt class="docutils literal">io_getevents(2)</tt>で得られる詳細な情報 ではなく単なる完了カウンタですが、<tt class="docutils literal">select(2)</tt>、<tt class="docutils literal">poll(2)</tt>、<tt class="docutils literal">epoll(2)</tt>などでeventfdが読み取り可能か見張るイベントループに使用できます。eventfdは面白い動作を備えており、カウンタが0の場合にeventfdから読み取ろうとするとブロックし、<tt class="docutils literal">epoll(2)</tt>などでも読み取り可能とは判断されません。</p>
<p><tt class="docutils literal">io_getevents(2)</tt>はシステム内部に蓄えられた完了情報を取り出します。情報の型は前掲の<tt class="docutils literal">struct io_event</tt>です。I/Oしたバイト数など対応するシステムコールの戻り値は<tt class="docutils literal">res</tt>メンバに代入されます。詳細は7raw_laio.c, 8raw_laio_ev.cを参照してください。</p>
<p>Linux AIOではaioという名前のカーネルスレッドも使用しますが、現状ではファイルクローズくらいしか主な仕事がありません。これはアプリケーションが<tt class="docutils literal">io_submit(2)</tt>後、かつAIO完了前に<tt class="docutils literal">O_DIRECT</tt>のファイルを<tt class="docutils literal">close(2)</tt>した場合に対応する処理で、アプリケーションが対象ファイルをもう使用しないと言ってもAIOリクエストが生きている限りはシステムはファイルをクローズしません。<tt class="docutils literal">io_submit(2)</tt>時に参照カウンタをインクリメントしてあるためです。AIO完了時に参照カウンタをデクリメントし、0になればファイルをクローズし、対応するシステム資源を解放します。それ以外にaioカーネ ルスレッドを使用するのはgadgetfs（USB Gadget。LinuxをUSB master（host）ではなく、slaveとして使用する場合に使うものらしい）しかないようです。</p>
<p class="footnote">その後、NFS も aio カーネルスレッドを使用するらしいということがわかりましたが、詳細は未確認です。</p>
<div class="section" id="libaio">
<h3>Libaio ライブラリ</h3>
<p>Glibcでは上記<tt class="docutils literal">io_submit(2)</tt>などに対応しておらず（少なくともv2.7では）、現状では別途開発された<a class="reference internal" href="#id17">libaio</a>を用いることになります。繰り返しになりますが、POSIXとは異なる独自インタフェースのため、やや使い勝手が異なります。しかし、前述のLinux AIOを理解しておけば有効に活用できるでしょう。</p>
<p><span class="target" id="id17">libaio</span>インタフェース</p>
<div class="highlight"><pre><span style="color: #BC7A00"># include&lt;libaio.h&gt;</span>

<span style="color: #408080; font-style: italic">/* libaioコア(と言って良いと思う) */</span>
<span style="color: #408080; font-style: italic">/* その他Linux AIOシステムコールも利用可能な様に定義されている */</span>
<span style="color: #B00040">int</span> io_queue_init(<span style="color: #B00040">int</span> maxevents, io_context_t <span style="color: #666666">*</span>ctxp);
<span style="color: #B00040">int</span> io_queue_release(io_context_t ctx);
<span style="color: #B00040">int</span> io_queue_run(io_context_t ctx);

<span style="color: #408080; font-style: italic">/* もう一つのコア:コールバック */</span>
<span style="color: #008000; font-weight: bold">typedef</span> <span style="color: #B00040">void</span>(<span style="color: #666666">*</span>io_callback_t)(io_context_t ctx, <span style="color: #008000; font-weight: bold">struct</span> iocb <span style="color: #666666">*</span>iocb,
    <span style="color: #B00040">long</span> res, <span style="color: #B00040">long</span> res2);
<span style="color: #B00040">void</span> io_set_callback(<span style="color: #008000; font-weight: bold">struct</span> iocb <span style="color: #666666">*</span>iocb, io_callback_t cb);

<span style="color: #408080; font-style: italic">/* 初期化関数 */</span>
<span style="color: #B00040">void</span> io_prep_pread(<span style="color: #008000; font-weight: bold">struct</span> iocb <span style="color: #666666">*</span>iocb, <span style="color: #B00040">int</span> fd, <span style="color: #B00040">void</span> <span style="color: #666666">*</span>buf, <span style="color: #B00040">size_t</span> count,
    <span style="color: #B00040">long</span> <span style="color: #B00040">long</span> offset);
<span style="color: #B00040">void</span> io_prep_pwrite(<span style="color: #008000; font-weight: bold">struct</span> iocb <span style="color: #666666">*</span>iocb, <span style="color: #B00040">int</span> fd, <span style="color: #B00040">void</span> <span style="color: #666666">*</span>buf, <span style="color: #B00040">size_t</span> count,
    <span style="color: #B00040">long</span> <span style="color: #B00040">long</span> offset);
<span style="color: #B00040">void</span> io_prep_preadv(<span style="color: #008000; font-weight: bold">struct</span> iocb <span style="color: #666666">*</span>iocb, <span style="color: #B00040">int</span> fd, <span style="color: #008000; font-weight: bold">const</span> <span style="color: #008000; font-weight: bold">struct</span> iovec <span style="color: #666666">*</span>iov,
    <span style="color: #B00040">int</span> iovcnt, <span style="color: #B00040">long</span> <span style="color: #B00040">long</span> offset);
<span style="color: #B00040">void</span> io_prep_pwritev(<span style="color: #008000; font-weight: bold">struct</span> iocb <span style="color: #666666">*</span>iocb, <span style="color: #B00040">int</span> fd, <span style="color: #008000; font-weight: bold">const</span> <span style="color: #008000; font-weight: bold">struct</span> iovec <span style="color: #666666">*</span>iov,
    <span style="color: #B00040">int</span> iovcnt, <span style="color: #B00040">long</span> <span style="color: #B00040">long</span> offset);
<span style="color: #B00040">void</span> io_prep_fsync(<span style="color: #008000; font-weight: bold">struct</span> iocb <span style="color: #666666">*</span>iocb, <span style="color: #B00040">int</span> fd);
<span style="color: #B00040">void</span> io_prep_fdsync(<span style="color: #008000; font-weight: bold">struct</span> iocb <span style="color: #666666">*</span>iocb, <span style="color: #B00040">int</span> fd);

<span style="color: #408080; font-style: italic">/* その他 */</span>
<span style="color: #B00040">int</span> io_fsync(io_context_t ctx, <span style="color: #008000; font-weight: bold">struct</span> iocb <span style="color: #666666">*</span>iocb, io_callback_t cb, <span style="color: #B00040">int</span> fd);
<span style="color: #B00040">int</span> io_fdsync(io_context_t ctx, <span style="color: #008000; font-weight: bold">struct</span> iocb <span style="color: #666666">*</span>iocb, io_callback_t cb, <span style="color: #B00040">int</span> fd);
<span style="color: #B00040">void</span> io_set_eventfd(<span style="color: #008000; font-weight: bold">struct</span> iocb <span style="color: #666666">*</span>iocb, <span style="color: #B00040">int</span> eventfd);
</pre></div>
<p><tt class="docutils literal">io_queue_init()</tt>、<tt class="docutils literal">io_queue_release()</tt>は単なる<tt class="docutils literal">io_setup(2)</tt>、<tt class="docutils literal">io_destroy(2)</tt>のラッパです。また、<tt class="docutils literal">io_prep_</tt>で始まる6つの初期化関数は、渡されたパラメータを前述の<tt class="docutils literal">struct iocb</tt>へ代入するだけのものです。<tt class="docutils literal">io_fsync()</tt>、<tt class="docutils literal">io_fdsync()</tt>も単なる便利関数で、初期化、<tt class="docutils literal">io_set_callback()</tt>、<tt class="docutils literal">io_submit(2)</tt>をまとめただけのもので、あまり存在意義がわかりません。これを用意するならば<tt class="docutils literal">io_pread()</tt>、<tt class="docutils literal">io_pwrite()</tt>なども用意するものではなかろうかとも思います。</p>
<p>libaioの特徴は<tt class="docutils literal">io_set_callback()</tt>、<tt class="docutils literal">io_queue_run()</tt>にあります。<tt class="docutils literal">io_queue_run()</tt>は<tt class="docutils literal">io_getevents(2)</tt>を発行し、完了したAIOリクエストに設定されたユーザ関数をコールします（コールバック）。AIOリクエストにコールバック関数を設定するのが<tt class="docutils literal">io_set_callback()</tt>です。libaioではコールバック関数が必須とされており、設定しないと実行時にエラーが発生します。この、コールバック関数が必須な点、またコールバックされる契機は自動ではなく、自ら<tt class="docutils literal">io_queue_run()</tt>をコールした時点であるという2点はlibaioの大きな特徴だと言えるでしょう。また、<tt class="docutils literal">struct iocb</tt>のaio_dataはlibaioが使用するため、ユーザが任意のデータを渡すことはできません。</p>
<p><a class="reference internal" href="#id18">libaioを用いたサンプルコード</a>を挙げます。</p>
<p><span class="target" id="id18">libaioを用いたサンプルコード</span> ― 9libaio_sample.c 要約</p>
<div class="highlight"><pre><span style="color: #BC7A00">#include &lt;libaio.h&gt;</span>

<span style="color: #B00040">char</span> <span style="color: #666666">*</span>fname;
<span style="color: #B00040">void</span> <span style="color: #0000FF">libaio_cb</span>(io_context_t ctx, <span style="color: #008000; font-weight: bold">struct</span> iocb <span style="color: #666666">*</span>iocb, <span style="color: #B00040">long</span> res, <span style="color: #B00040">long</span> res2)
{
    printf(<span style="color: #BA2121">&quot;%s: io_submit from fd %d completed</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">&quot;</span>,
        __func__, iocb <span style="color: #666666">-&gt;</span> aio_fildes);
    printf(<span style="color: #BA2121">&quot;%s, %ld read</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">&quot;</span>, fname, res);
}

<span style="color: #B00040">int</span> <span style="color: #0000FF">main</span> (<span style="color: #B00040">int</span> argc, <span style="color: #B00040">char</span> <span style="color: #666666">*</span>argv[])
{
    <span style="color: #B00040">int</span> fd;
    <span style="color: #B00040">char</span> <span style="color: #666666">*</span>p;
    io_context_t libaio;
    <span style="color: #008000; font-weight: bold">struct</span> iocb <span style="color: #666666">*</span>pcb, laiocb;

    fd <span style="color: #666666">=</span> open(argv[<span style="color: #666666">1</span>], O_RDONLY <span style="color: #666666">|</span> O_DIRECT);
    posix_memalign((<span style="color: #B00040">void</span> <span style="color: #666666">*</span>)<span style="color: #666666">&amp;</span>p, <span style="color: #666666">512</span>, BUFSIZ);

    fname <span style="color: #666666">=</span> argv[<span style="color: #666666">1</span>];
    io_queue_init(<span style="color: #666666">1</span>, <span style="color: #666666">&amp;</span>libaio);
    io_prep_pread(<span style="color: #666666">&amp;</span>laiocb, fd, p, BUFSIZ, <span style="color: #666666">0</span>);
    io_set_callback(<span style="color: #666666">&amp;</span>laiocb, libaio_cb);

    pcb <span style="color: #666666">=</span> <span style="color: #666666">&amp;</span>laiocb;
    io_submit(libaio, <span style="color: #666666">1</span>, <span style="color: #666666">&amp;</span>pcb);

    <span style="color: #408080; font-style: italic">/* do other jobs */</span>

    io_queue_run(libaio);
}
</pre></div>
<p>9libaio_sample.cでは<tt class="docutils literal">io_queue_run()</tt>を終えてもAIOが完了しているとは限りません。このライブラリは<tt class="docutils literal">io_getevents(2)</tt>の<tt class="docutils literal">min_nr</tt>に0を渡しており、ブロックしません（というか、後述するように自分でシステムコール発行前に完了情報の有無を直接調べるので、完了していなければシステムコールを発行すらしません）。libaioではコールバックされたか否かで完了を判断するというアプローチです。</p>
<p>Linux AIOが自動通知機能を備えていない以上、アプリケーションがライブラリをコールして初めてコールバックされる動作は当然とも言えますが、プログラマからは敬遠されるかもしれません。この点はアプリケーションをマルチスレッド化し、常駐する別スレッドが<tt class="docutils literal">io_queue_run()</tt>相当の処理（ただしブロックする）を実行することで対応可能と思われますが、マルチスレッド化に伴うプログラミング上の注意点が増えます。これもまたプログラマからは敬遠される要素になるかもしれません。</p>
<p>libaioにはもう一つ隠れた特徴があります。<tt class="docutils literal">io_setup(2)</tt>はシステム内にメモリ領域を確保しますが、実はユーザ空間にもマッピングしてくれ、得られる<tt class="docutils literal">aio_context_t</tt>はそのポインタです。この動作は、例えば<tt class="docutils literal">open(2)</tt>がシステム内に確保したメモリ領域をユーザには見せず、単にファイルディスクリプタを返す動作とは似て異なるもので、善し悪しの意見が分かれるところかもしれません。libaioではこの動作を利用し（というかLinux AIOとlibaioは同じ人物が開発したので、自分用にこんなマッピングを実装したのかもしれません）、<tt class="docutils literal">io_getevents(2)</tt>をラッピングし、事前にシステム内のメモリ領域を参照し、完了したAIOリクエストが存在する場合にのみ本当のシステムコールを発行するようにしています。これはシステムコール発行回数を削減し効率化を狙った動作と思われます。</p>
<p>Linux AIOがeventfdに対応しているように、libaioからも利用できます。サンプルコード10libaio_sample_ev.cを一式に含めておきます。eventfdを用いると、マルチスレッド化 や<tt class="docutils literal">io_queue_run()</tt>相当の処理を実装せずとも、ブロックする動作が可能となります（もちろん必要ならばですが）。</p>
<p>（初出: H22/4）</p>
<p>Copyright © 2010 SENJU Jiro</p>
<p class="footnote">本記事のサンプルコードは、以下のリンクよりダウンロードすることができます。記事と合わせてご参照ください。<br />
[<a href="http://www.oreilly.co.jp/pub/pg_high/ph20100730.tar.bz2">サンプルコード</a>]<br />
また、本稿で取り上げた内容は、記事の執筆時の情報に基づいており、その後の実装の進展、変更等により、現在の情報とは異なる場合があります。<br />
本連載「プログラマーズ・ハイ」では読者からのご意見、ご感想を切望しています。今後取り上げるテーマなどについてもご意見をお寄せください。</p>
</div>

]]>
    </content>
</entry>

<entry>
    <title>バッファキャッシュとAIO（2）</title>
    <link rel="alternate" type="text/html" href="http://www.oreilly.co.jp/community/blog/2010/09/buffer-cache-and-aio-part2.html" />
    <id>tag:www.oreilly.co.jp,2010:/community/blog//4.660</id>

    <published>2010-09-02T07:00:00Z</published>
    <updated>2010-12-08T09:37:08Z</updated>

    <summary> プロセスがブロックする要因の一つにファイルI/Oがあります。これを同期I/Oと言いますが、POSIXではAIO（非同期 I/O、Asynchronous I/O）も定義しており、I/O中でもプロセス...</summary>
    <author>
        <name>千住治郎</name>
        
    </author>
    
        <category term="Programmer&apos;s High" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="ja" xml:base="http://www.oreilly.co.jp/community/blog/">
        <![CDATA[<img alt="http://www.oreilly.co.jp/community/blog/images/union/pg_high_logo.png" src="http://www.oreilly.co.jp/community/blog/images/union/pg_high_logo.png" />
<p class="lead">プロセスがブロックする要因の一つにファイルI/Oがあります。これを同期I/Oと言いますが、POSIXではAIO（非同期 I/O、Asynchronous I/O）も定義しており、I/O中でもプロセスがブロックせず他の処理を進められるようになります。
今回は、バッファキャッシュを意識したさまざまなファイルI/Oについて解説します。</p>
<div class="section" id="id12">
<h2>メモリマップ I/O</h2>
<p>ファイルI/Oの一種にメモリマップI/O、<tt class="docutils literal">mmap(2)</tt>があります。<tt class="docutils literal">mmap(2)</tt>（および<tt class="docutils literal">mmap2(2)</tt>）はオープンされたファイルをプロセスアドレス空間へマッピングするもので、例えばアプリケーション内に領域を用意し、ファイルを読み取る動作は次のようにも実装できます。</p>
<p>3mmap.c 要約</p>
<div class="highlight"><pre>{
    <span style="color: #B00040">char</span> a[N];

    fd <span style="color: #666666">=</span> open(path, O_RDONLY);
    read(fd, a, N)
    printf(<span style="color: #BA2121">&quot;%.*s</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">&quot;</span>, N, a);
}

{
    <span style="color: #B00040">char</span> <span style="color: #666666">*</span>p;

    fd <span style="color: #666666">=</span> open(path, O_RDONLY);
    p <span style="color: #666666">=</span> mmap(<span style="color: #008000">NULL</span>, sysconf(_SC_PAGESIZE), PROT_READ, MAP_SHARED, fd, <span style="color: #666666">0</span>);
    printf(<span style="color: #BA2121">&quot;%.*s</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">&quot;</span>, N, p);
}
</pre></div>
<p>上記2つの関数は同じ内容を表示します。<tt class="docutils literal">read(2)</tt>を用いる場合は、通常静的に宣言した文字配列や<tt class="docutils literal">malloc(3)</tt>により割り当てたメモリ領域を事前に用意しますが、<tt class="docutils literal">mmap(2)</tt>を用いると用意した領域がそのままファイルの内容になります。このためメモリ間のコピー（上の例ではバッファキャッシュからアプリケーション内の配列aへのコピー）を削減できます。対象ファイルがまだ読み取られておらず、バッファキャッシュに存在しない場合は、<tt class="docutils literal">mmap(2)</tt>が返したメモリ領域を参照すると、システム側でディクスから自動的に読み取ります。</p>
<p>上の例では読み取りしか行っていませんが、書き込みも同様に可能です。<tt class="docutils literal">open(2)</tt>、<tt class="docutils literal">mmap(2)</tt>のパラメータを書き込み用に変え、得られたメモリ領域内を変更すると、ファイル書き込みと同等の動作となります。この場合、メモリ領域がディスクへ書き戻す必要があることを通知するのは<tt class="docutils literal">msync(2)</tt>です。<tt class="docutils literal">MS_ASYNC</tt>を指定すると、通知するだけでシステムコールは終了します。<tt class="docutils literal">MS_SYNC</tt>を指定すると、通知に加え、内部で<tt class="docutils literal">fsync(2)</tt>相当の処理も行われ、ディスクにまで書き戻します。</p>
<p><tt class="docutils literal">mmap(2)</tt>したメモリ領域は、アプリケーション終了時に自動的に<tt class="docutils literal">munmap(2)</tt>され、また<tt class="docutils literal">munmap(2)</tt>は<tt class="docutils literal">msync(2)</tt>を包含するため、<tt class="docutils literal">mmap(2)</tt>したメモリ領域を更新後にアプリケーションが終了するならば、明示的な<tt class="docutils literal">msync(2)</tt>は省略可能です。しかし、メモリ更新から<tt class="docutils literal">munmap(2)</tt>まで時間があく可能性がある、またはすぐにデータをディスクにまで保存したいなどの場合には<tt class="docutils literal">msync(2)</tt>を発行します。<tt class="docutils literal">mmap(2)</tt>のパラメータに<tt class="docutils literal">MAP_SHARED</tt>を指定すると、アドレスこそ変換されますがこの領域の参照はシステムが管理するバッファキャッシュの直接参照に相当します。</p>
<p>さらに、ファイルポジションを移動する<tt class="docutils literal">lseek(2)</tt>もユーザ空間だけで解決するアドレス演算で代替でき（上の例では<tt class="docutils literal">p+1</tt>すればファイル内の2バイト目が参照できる）、発行システムコール数を削減できます。</p>
<p><tt class="docutils literal">mmap(2)</tt>は効率的なI/Oに大きく貢献しますが、若干の注意点があります。</p>
<ul class="simple">
<li>システムのメモリ管理に近付くという性質上、パラメータの<tt class="docutils literal">address</tt>、<tt class="docutils literal">length</tt>、<tt class="docutils literal">offset</tt>はメモリページサイズの倍数でなければならない（<tt class="docutils literal">length</tt>にアラインメントを必要としないシステムもある）。</li>
<li>ファイルサイズが小さい場合はプロセスアドレス空間が無駄になる。例えばページサイズが4,096バイトの場合にサイズが1バイトしかないファイルを<tt class="docutils literal">mmap(2)</tt>すると、内容があるのは1バイトだけであり、残り4,095バイトはゼロで埋められ、プロセスアドレス空間を無駄に消費する。</li>
<li>現代では32ビットアドレス空間は不足する場合もある。
上述の無駄に加え、フラグメンテーションが発生する恐れもあり、大きな空き領域が必要な場合に連続した領域が得られなくなることが考えられる。</li>
</ul>
<p>このため、一般的にはある程度のサイズ（少なくともメモリページサイズ以上）を持つファイルでなければ、<tt class="docutils literal">mmap(2)</tt>の効果は薄れると考えられます。やはりLinux固有のシステムコールですが、<tt class="docutils literal">remap_file_pages(2)</tt>というのもあり、同じファイルに対し<tt class="docutils literal">mmap(2)</tt>を複数回発行する場合、2回目以降は<tt class="docutils literal">remap_file_pages(2)</tt>を用いた方が効率向上が期待できます。<tt class="docutils literal">mmap(2)</tt>を発行すると、システム内部ではメモリ管理用の構造体を新たに作成しますが、<tt class="docutils literal">remap_file_pages(2)</tt>を用いると既存の内部構造体を用いるためです。</p>
</div>]]>
        <![CDATA[<div class="section" id="sendfile-2-pipe-2">
<h2><tt class="docutils literal">sendfile(2)</tt>、<tt class="docutils literal">pipe(2)</tt>、その他のバッファ操作</h2>
<p>ディスク上のファイルデータをソケットへ書き込む際に<tt class="docutils literal">write(2)</tt>/<tt class="docutils literal">send(2)</tt>する方法がありますが、この場合は<tt class="docutils literal">sendfile(2)</tt>を用いた方が効率的です。バッファコピー削減に加え、<tt class="docutils literal">mmap(2)</tt>によるプロセスアドレス空間の消費も削減できる、バッファキャッシュからソケットバッファへのカーネル内メモリコピー（実際にはコピーせずメモリページを管理する構造体の参照カウント、参照先を操作することで高速化を図れる）処理です。ファイルの内容に関知せずソケットに渡すだけの場合に効果的です。<tt class="docutils literal">sendfile(2)</tt>は以前の記事「<a class="reference internal" href="#pthreadepoll">インターネットサーバでのPthreadとepoll</a>」でも取り上げたため、サンプルコードはそちらを参照してください。</p>
<p><span class="target" id="pthreadepoll">インターネットサーバでのPthreadとepoll</span></p>
<ul class="simple">
<li>Part 1: <a class="reference external" href="http://www.oreilly.co.jp/community/blog/2010/03/pthread-epoll-inet-server-part1.html">http://www.oreilly.co.jp/community/blog/2010/03/pthread-epoll-inet-server-part1.html</a></li>
<li>Part 2: <a class="reference external" href="http://www.oreilly.co.jp/community/blog/2010/03/pthread-epoll-inet-server-part2.html">http://www.oreilly.co.jp/community/blog/2010/03/pthread-epoll-inet-server-part2.html</a></li>
</ul>
<div class="section" id="pipe-2">
<h3><tt class="docutils literal">pipe(2)</tt></h3>
<p>元来Unixではさまざまなリソースがファイルディスクリプタに対応付けられますが、<tt class="docutils literal">pipe(2)</tt>もその一つで、2つのファイルディスクリプタが対応付けられます。内部ではパイプバッファという専用のメモリが確保され、2つのファイルディスクリプタを用い、パイプバッファを読み書きします。パイプバッファはディスク上の通常ファイルに対応するバッファキャッシュとは厳密には異なりますが、ユーザプログラムからは通常のファイルディスクリプタにしか見えませんので、違いを意識する必要はあまりありません。しかし、通常ファイルとは異なる、次の動作がプログラマを悩ませることがあります。</p>
<blockquote>
<ul class="simple">
<li>読み取れるデータが存在しなければ、パイプからの読み取りは(通常)ブロックする。</li>
<li>パイプバッファが一杯ならば、パイプへの書き込みは(通常)ブロックする。</li>
</ul>
</blockquote>
<p>例えば次のようなコードにはバグがあります。これは以前にあるPerlライブラリに実在したもので、実際にはCではなくPerlで記述されていました。</p>
<p>パイプ処理のアプリケーションバグ</p>
<div class="highlight"><pre>{
    <span style="color: #B00040">int</span> fds[<span style="color: #666666">2</span>];
    pid_t pid;

    pipe(fds);
    pid <span style="color: #666666">=</span> fork();
    <span style="color: #008000; font-weight: bold">if</span> (<span style="color: #666666">!</span>pid) {
        <span style="color: #408080; font-style: italic">/* 子プロセス */</span>
        write(fds[<span style="color: #666666">1</span>], ...);
        <span style="color: #008000; font-weight: bold">return</span> <span style="color: #666666">0</span>;
    } <span style="color: #008000; font-weight: bold">else</span> <span style="color: #008000; font-weight: bold">if</span> (pid <span style="color: #666666">&gt;</span> <span style="color: #666666">0</span>) {
        <span style="color: #408080; font-style: italic">/* 親プロセス */</span>
        wait(<span style="color: #008000">NULL</span>);
        read(fds[<span style="color: #666666">0</span>], ...);
    }
}
</pre></div>
<p>子プロセスが書き出した内容を親プロセスが読み取るというよくある動作ですが、このコードでは子プロセスがパイプバッファが一杯になるまで書き込むと、両プロセスともブロックしてしまいます。その原因は前述のパイプの動作を読み返すと分かります。パイプバッファが一杯になると子プロセスはそれ以上<tt class="docutils literal">write(2)</tt>できなくなり、パイプバッファに空きが出るのを待ちます。一方親プロセスは子プロセスの終了を待つため、どちらも処理を進められなくなります。このバグは親プロセスがパイプから<tt class="docutils literal">read(2)</tt>する先に<tt class="docutils literal">wait(2)</tt>しているのが(ひとつの)原因なので、まず<tt class="docutils literal">read(2)</tt>するようにすれば期待通りに動作するでしょう。もちろん一度の<tt class="docutils literal">read(2)</tt>で書き込まれたデータをすべて読み取れる保証がなければ、複数回<tt class="docutils literal">read(2)</tt>することになるでしょう。その他の対策として、<tt class="docutils literal">fcntl(2)</tt>を用い<tt class="docutils literal">O_NONBLOCK</tt>フラグを設定するノンブロッキングI/Oという方法も考えられます。</p>
<p>さらに、Linuxではバッファコピーを意識したシステムコールを追加しました。比較的地味で使用する場面も多くないと思いますが、簡単に紹介しておきます。</p>
<dl class="docutils">
<dt><tt class="docutils literal">splice(2)</tt></dt>
<dd><p class="first">2つのファイルディスクリプタを指定し、対応するバッファをカーネル内でコピーする。ユーザ空間を経由しない。効率的なファイルコピーに使えるが、現在のインタフェースではファイルディスクリプタのうち一つはパイプでなければならないとされている。</p>
<p>Linux2.6.23（平成19年10月）では<tt class="docutils literal">sendfile(2)</tt>の実装に<tt class="docutils literal">splice(2)</tt>のコアを用いるように変更された。</p>
<p class="last">以前の記事「<a class="reference internal" href="#unionmountunion-type-filesystem">UnionMountとUnion-type Filesystem</a>」で紹介したUnionMountではカーネル内で<tt class="docutils literal">splice(2)</tt>のコアを用い、パイプを用いず通常ファイルをコピーしている（カーネル内からは一つはパイプという仕様を回避できる）。ただしsparseファイルには対応しておらず、まるごとコピーになってしまう。</p>
</dd>
<dt><tt class="docutils literal">tee(2)</tt></dt>
<dd><tt class="docutils literal">splice(2)</tt>同様にカーネル内でメモリコピーするが、ファイルディスクリプタは2つともパイプでなければならない。</dd>
<dt><tt class="docutils literal">vmsplice(2)</tt></dt>
<dd>splice(2) と同等の機能だが、パイプのファイルディスクリプタを1つとユーザ空間を指すstruct iovecを渡し、ユーザ空間をパイプへマッピングする。</dd>
</dl>
<p><span class="target" id="unionmountunion-type-filesystem">UnionMountとUnion-type Filesystem</span></p>
<ul class="simple">
<li>Part1: <a class="reference external" href="http://www.oreilly.co.jp/community/blog/2010/02/union-mount-uniontype-fs-part-1.html">http://www.oreilly.co.jp/community/blog/2010/02/union-mount-uniontype-fs-part-1.html</a></li>
<li>Part2: <a class="reference external" href="http://www.oreilly.co.jp/community/blog/2010/02/union-mount-uniontype-fs-part2.html">http://www.oreilly.co.jp/community/blog/2010/02/union-mount-uniontype-fs-part2.html</a></li>
</ul>
<p><tt class="docutils literal">splice(2)</tt>と<tt class="docutils literal">tee(2)</tt>を用いたサンプルコードは<tt class="docutils literal">tee(2)</tt>のマニュアルページに記述されています（man-pages v2.41以降）。</p>
<p>以前の<tt class="docutils literal">vmsplice(2)</tt>にはセキュリティ問題がありました。<tt class="docutils literal">struct iovec</tt>を用いたユーザ空間のマッピングという動作に由来する問題で、<tt class="docutils literal">struct iovec</tt>の値を処理した後にアドレスの有効性をしなかったため、任意のユーザコードをルート権限で実行可能にできてしまうというものでした。実際の被害などは報告されませんでしたが、2.6.25、2.6.24.2で修正されました。</p>
<p>上記3つのシステムコールはいずれもパイプに対するI/Oという仮面を被っていますが、システム内部バッファ（ユーザ空間ではないバッファ）を利用することを目的としたものです。また、「コピー」ではなく「移動」する場合には<tt class="docutils literal">SPLICE_F_MOVE</tt>フラグを指定できます。しかし、マニュアルにはそう記述されていても、実はLinux 2.6.21でこのフラグは削除されています。約三年前です。復活しないということは必要性が低いということでしょう。</p>
<p class="footnote">平成22年5月にリリースされたLinux 2.6.34では、パイプバッファのサイズを取得/変更するインタフェース、<tt class="docutils literal">fcntl(F_GETPIPE_SZ)</tt>、<tt class="docutils literal">fcntl(F_SETPIPE_SZ)</tt>が追加実装されました。</p>
<p>上記ではメモリコピーと記述しましたが、内部で実際にコピーされるとは限りません。システムが管理するメモリページの参照カウントを増やし、データを共有する方式が採られます。</p>
</div>
</div>
<div class="section" id="direct-i-o">
<h2>Direct I/O</h2>
<p>Direct I/Oはバッファキャッシュを介さないファイルI/Oで、ブロックデバイスと直接データをやりとりします。詳細は後述しますが、LinuxネイティブなAIOシステムコール<tt class="docutils literal">io_submit(2)</tt>では、Direct I/Oが指定されなければ、非同期動作にはなりません。</p>
<p>Direct I/Oは二重バッファリングを無駄とみなす考えから、システムレベルのバッファリングを省き、アプリケーションレベルのバッファリングと併用されることがあります。バッファリングを一切せずにDirect I/Oを使用しても単に性能を落とすだけです。同じファイルに通常のI/O（buffered I/O）や<tt class="docutils literal">mmap(2)</tt>によるメモリマップI/Oと混在させても性能低下が考えられます。</p>
<p>Direct I/Oによるファイル書き込みは、まずバッファキャッシュがあれば、これをディスクへ書き戻し、そのバッファキャッシュを無効（invalid）にします。その後本来要求されたデータをディスクへ書き込み、この際に使用したバッファキャッシュも再び無効にします（<a class="reference internal" href="#id13">図6</a>）。同様にファイル読み取りも、まずバッファキャッシュをディスクへ書き戻し（有効なバッファキャッシュがあれば）、その後ディスクから直接読み取ります。</p>
<p><span class="target" id="id13">図6</span> Direct I/Oの動作</p>
<img alt="fig/aio-fig06.png" src="fig/aio-fig06.png" />
<p><tt class="docutils literal">mmap(2)</tt>でも必要とされたアラインメントはDirect I/Oでも必須となり、特に（よくbufferと名付けられる）アプリケーション内作業領域のアドレスにもアラインメントが要求されます。これには<tt class="docutils literal">posix_memalign(3)</tt>などを用いると良いでしょう。現在のLinuxでは512バイトでアラインメントします。簡単な例を挙げておきます。</p>
<p>4dio.c 要約</p>
<div class="highlight"><pre>{
    <span style="color: #B00040">char</span> <span style="color: #666666">*</span>p;

    fd <span style="color: #666666">=</span> open(path, O_RDWR <span style="color: #666666">|</span> O_DIRECT);
    posix_memalign((<span style="color: #B00040">void</span> <span style="color: #666666">*</span>)<span style="color: #666666">&amp;</span>p, <span style="color: #666666">512</span>, BUFSIZ);
    memset(p, <span style="color: #BA2121">&#39;a&#39;</span>, BUFSIZ);
    write(fd, p, BUFSIZ);
}
</pre></div>
<p>一般に、Direct I/Oはデータベースなど専用の形式を持つファイルへのI/Oなどに有効と言われています。データベースソフトウェア（形態は単独アプリケーションかもしれませんし、専用ライブラリかもしれません）が自身でバッファリングを実装し、ディスクへ書き出す内容、順序も充分に意識すれば、電源断など不意の事故が発生しても、ディスク上での整合性を完全に、もしくは容易に修復可能な程度に維持できるでしょう。この場合一度のシステムコールで複数のアプリケーションバッファをI/Oする<tt class="docutils literal">readv(2)</tt>、<tt class="docutils literal">writev(2)</tt>の利用が考えられます。しかし、引数の<tt class="docutils literal">struct iovec</tt>ではファイル内オフセットを指定できませんので、<tt class="docutils literal">pread(2)</tt>、<tt class="docutils literal">pwrite(2)</tt>を繰り返すことになるかもしれません。Linux 2.6.30以降では、両システムコールを合体させた<tt class="docutils literal">preadv(2)</tt>、<tt class="docutils literal">pwritev(2)</tt>が追加されたので、こちらの方が役に立つかもしれません。後述する<tt class="docutils literal">io_submit(2)</tt>に渡す<tt class="docutils literal">struct iocb</tt>でも、オフセット指定可能です。</p>
<p>また、ブロックデバイスがハードディスクではなく、近年普及しているSSD、フラッシュメモリの場合にも有効と考えられます。ブロックデバイスがメモリの場合には、バッファキャッシュの有効性がずっと下がるため、使用しなければメモリ節約につながりますし、バッファキャッシュの管理コストを削減できれば、速度面での向上もさらに期待できます。</p>
<p>同様の目的をもった機能は、実行形式のファイルに対しても実装されています。通常コマンドを実行する際には、実行形式のファイルをメモリ上にロードしますが、このときブロックデバイスがすでにメモリならばロードを省略しようという機能で、XIP（execute in place）と呼ばれます。ただしXIP対応のデバイス、ファイルシステムが必要になります。Linuxのtmpfsでは対応していませんが（というかバックエンドとなるブロックデバイスを持たない）、ramdiskでは対応しています（<tt class="docutils literal">CONFIG_BLK_DEV_XIP</tt>を有効にする必要がある）。繰り返しになりますが、自身でバッファリングしないアプリケーションがディスク上のファイルに対してDirect I/Oを使用しても、通常は遅くなるだけでメリットがない点に注意してください。</p>
</div>
<div class="section" id="id14">
<h2>その他のI/O</h2>
<p>ディスク上の通常ファイル以外に対する使用が主ですが、上記のI/O方式以外にも、I/Oが可能になったことをシグナルで通知するシグナルドリブンI/O、データが存在しないなどの理由でI/Oをブロックするような場合でもブロックさせないノンブロッキングI/Oがありますが、本記事では割愛します。</p>
<p>さて、ここまでファイル I/O全般を簡単に振り返りました。思ったよりも長くなってしまいましたが、次回はいよいよ本題のAIOに取り掛かります。</p>
<p>（初出: H22/4）</p>
<p>Copyright © 2010 SENJU Jiro</p>
<p class="footnote">本記事のサンプルコードは、以下のリンクよりダウンロードすることができます。記事と合わせてご参照ください。<br />
[<a href="http://www.oreilly.co.jp/pub/pg_high/ph20100730.tar.bz2">サンプルコード</a>]<br />
また、本稿で取り上げた内容は、記事の執筆時の情報に基づいており、その後の実装の進展、変更等により、現在の情報とは異なる場合があります。
本連載「プログラマーズ・ハイ」では読者からのご意見、ご感想を切望しています。今後取り上げるテーマなどについてもご意見をお寄せください。</p>
</div>
]]>
    </content>
</entry>

<entry>
    <title>バッファキャッシュとAIO（1）</title>
    <link rel="alternate" type="text/html" href="http://www.oreilly.co.jp/community/blog/2010/08/buffer-cache-and-aio-part1.html" />
    <id>tag:www.oreilly.co.jp,2010:/community/blog//4.659</id>

    <published>2010-08-18T01:00:00Z</published>
    <updated>2011-06-30T00:12:05Z</updated>

    <summary> プロセスがブロックする要因の一つにファイルI/Oがあります。これを同期I/Oと言いますが、POSIXではAIO（非同期 I/O、Asynchronous I/O）も定義しており、I/O中でもプロセス...</summary>
    <author>
        <name>千住治郎</name>
        
    </author>
    
        <category term="Programmer&apos;s High" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="ja" xml:base="http://www.oreilly.co.jp/community/blog/">
        <![CDATA[<img alt="http://www.oreilly.co.jp/community/blog/images/union/pg_high_logo.png" src="http://www.oreilly.co.jp/community/blog/images/union/pg_high_logo.png" />
<p class="lead">プロセスがブロックする要因の一つにファイルI/Oがあります。これを同期I/Oと言いますが、POSIXではAIO（非同期 I/O、Asynchronous I/O）も定義しており、I/O中でもプロセスがブロックせず他の処理を進められるようになります。
本記事ではバッファキャッシュからファイル I/Oを解説し、Linuxの<tt class="docutils literal">io_submit(2)</tt>を用いたPOSIX準拠のAIOライブラリを試作してみます。</p>
<h2>ファイルI/Oとバッファキャッシュ</h2>
<p><tt class="docutils literal">io_submit(2)</tt>ではDirect I/Oを用いますが、ライブラリの試作へ進む前にまずファイルI/Oのバッファ（バッファキャッシュ）について整理します。実は単にバッファと言ってしまうと誤解される場面が多くあり、例えばプログラミング入門一般としてファイルI/Oを取り上げる際には、</p>
<ul class="simple">
<li>CPUの動作は速い。ディスクの動作は遅い。</li>
<li>両者の間に速度差を緩和する緩衝地帯を設けないと、CPUの速度が犠牲にされてしまう。</li>
<li>このため、ファイルバッファを用いる。</li>
</ul>
<p>といった説明があり、よく以下のようなサンプルコードが提示されます。</p>
<p>ファイルI/Oサンプルコード</p>
<div class="highlight"><pre>main()
{
<span style="color: #BC7A00">    # define SZ 16</span>
    <span style="color: #B00040">int</span> fd;
    <span style="color: #B00040">char</span> buffer[SZ];

    fd <span style="color: #666666">=</span> open(argv[<span style="color: #666666">1</span>], O_WRONLY <span style="color: #666666">|</span> O_CREAT <span style="color: #666666">|</span> O_TRUNC, <span style="color: #666666">0644</span>);

    memset(buffer, <span style="color: #BA2121">&#39;a&#39;</span>, SZ);
    write(fd, buffer, SZ);
}
</pre></div>
<p>先ほどの説明の後で<tt class="docutils literal">buffer</tt>という名前の配列を見ると、これがCPUとディスクの速度差を緩和する緩衝体（バッファ）の役割をするもなのかと思ってしまいそうです。しかし現代の一般的な環境では、この<tt class="docutils literal">buffer</tt>にはあまりそのような意味合いはなく、単なる作業用のメモリ領域に過ぎません。細部を説明する前に、あと2つサンプルコードを挙げます。以降のサンプルコードは付属のソースファイル一式に含まれているので、詳細はそちらを参照してください。</p>
]]>
        <![CDATA[<div class="section" id="fread-3">
<h3><tt class="docutils literal">fread(3)</tt>が正しくない?</h3>
<p>まずは以下のコードを見て下さい。</p>
<blockquote>
<span class="target" id="rw-c">1rw.c</span>（要約）</blockquote>
<div class="highlight"><pre><span style="color: #BC7A00"># ifndef PhBufferSize</span>
<span style="color: #BC7A00"># define PhBufferSize 16</span>
<span style="color: #BC7A00"># endif</span>

main()
{
    <span style="color: #008000; font-weight: bold">struct</span> {
        <span style="color: #B00040">int</span> fd;
        <span style="color: #B00040">char</span> buffer[PhBufferSize];
    } a[<span style="color: #666666">2</span>];

    a[<span style="color: #666666">0</span>].fd <span style="color: #666666">=</span> open(argv[<span style="color: #666666">1</span>], O_WRONLY <span style="color: #666666">|</span> O_CREAT <span style="color: #666666">|</span> O_TRUNC, <span style="color: #666666">0644</span>);
    a[<span style="color: #666666">1</span>].fd <span style="color: #666666">=</span> open(argv[<span style="color: #666666">1</span>], O_RDONLY);

    memset(a[<span style="color: #666666">0</span>].buffer, <span style="color: #BA2121">&#39;a&#39;</span>, PhBufferSize);
    write(a[<span style="color: #666666">0</span>].fd, a[<span style="color: #666666">0</span>].buffer, PhBufferSize);
    ssz <span style="color: #666666">=</span> read(a[<span style="color: #666666">1</span>].fd, a[<span style="color: #666666">1</span>].buffer, PhBufferSize);
    p <span style="color: #666666">=</span> <span style="color: #BA2121">&quot;equivalent&quot;</span>;
    <span style="color: #008000; font-weight: bold">if</span> (memcmp(a[<span style="color: #666666">0</span>].buffer, a[<span style="color: #666666">1</span>].buffer, PhBufferSize))
        p <span style="color: #666666">=</span> <span style="color: #BA2121">&quot;different&quot;</span>;
    printf(<span style="color: #BA2121">&quot;PhBufferSize %d, %zd bytes read, %s</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">&quot;</span>,
        PhBufferSize, ssz, p);
}
</pre></div>
<p><span class="target" id="frw-c">2frw.c</span>要約</p>
<div class="highlight"><pre><span style="color: #BC7A00"># ifndef PhBufferSize</span>
<span style="color: #BC7A00"># define PhBufferSize 16</span>

<span style="color: #BC7A00"># endif</span>

main()
{
    <span style="color: #008000; font-weight: bold">struct</span> {
        <span style="color: #B00040">FILE</span> <span style="color: #666666">*</span>fp;
        <span style="color: #B00040">char</span> buffer[PhBufferSize];
    } a[<span style="color: #666666">2</span>];

    a[<span style="color: #666666">0</span>].fp <span style="color: #666666">=</span> fopen(argv[<span style="color: #666666">1</span>], <span style="color: #BA2121">&quot;w+&quot;</span>);
    a[<span style="color: #666666">1</span>].fp <span style="color: #666666">=</span> fopen(argv[<span style="color: #666666">1</span>], <span style="color: #BA2121">&quot;r&quot;</span>);

    memset(a[<span style="color: #666666">0</span>].buffer, <span style="color: #BA2121">&#39;a&#39;</span>, PhBufferSize );
    fwrite(a[<span style="color: #666666">0</span>].buffer, <span style="color: #008000; font-weight: bold">sizeof</span>(<span style="color: #666666">*</span>a[<span style="color: #666666">0</span>].buffer), PhBufferSize, a[<span style="color: #666666">0</span>].fp);
    sz <span style="color: #666666">=</span> fread(a[<span style="color: #666666">1</span>].buffer, <span style="color: #008000; font-weight: bold">sizeof</span>(<span style="color: #666666">*</span>a[<span style="color: #666666">1</span>].buffer), PhBufferSize, a[<span style="color: #666666">1</span>].fp);
    p <span style="color: #666666">=</span> <span style="color: #BA2121">&quot;equivalent&quot;</span>;
    <span style="color: #008000; font-weight: bold">if</span> (memcmp(a[<span style="color: #666666">0</span>].buffer, a[<span style="color: #666666">1</span>].buffer, PhBufferSize))
        p <span style="color: #666666">=</span> <span style="color: #BA2121">&quot;different&quot;</span>;
    printf(<span style="color: #BA2121">&quot;PhBufferSize %d, %zu bytes read, %s</span><span style="color: #BB6622; font-weight: bold">\n</span><span style="color: #BA2121">&quot;</span>,
        PhBufferSize, sz, p);
}
</pre></div>
<p>1rw.cも2frw.cも次のような単純な内容です。</p>
<ul class="simple">
<li>指定されたファイルを書き込み用にオープンする。</li>
<li>同じファイルを読み取り用にオープンする。</li>
<li>ファイルへ書き込む。</li>
<li>ファイルから読み取る。</li>
<li>読み取った内容が書き込んだ内容と一致するかを確認する。</li>
</ul>
<p>しかしこの2つは、ファイルの扱い方が異なります。<a class="reference internal" href="#rw-c">1rw.c</a>はシステムコールを用いたファイルディスクリプタを使用し、<a class="reference internal" href="#frw-c">2frw.c</a>ではstdioを用いた<tt class="docutils literal">FILE</tt>ポインタです。単純に<tt class="docutils literal">open(2)</tt>を<tt class="docutils literal">fopen(3)</tt>へ、<tt class="docutils literal">write(2)</tt>を<tt class="docutils literal">fwrite(3)</tt>へ、<tt class="docutils literal">read(2)</tt>を<tt class="docutils literal">fread(3)</tt>へ、それぞれ対応させ書き換えただけとも言えます。</p>
<p>こんな単純なソースコードはコンパイル、実行するまでもなく、結果は一致するに決まっていると思われるかもしれませんが、実際には異なる結果になります。</p>
<p>1rwと2frwの実行結果</p>
<div class="highlight"><pre><span style="color: #19177C">$ </span>./1rw /tmp/test
PhBufferSize 16, 16 bytes <span style="color: #008000">read</span>, equivalent

<span style="color: #19177C">$ </span>./2frw /tmp/test

PhBufferSize 16, 0 bytes <span style="color: #008000">read</span>, different
</pre></div>
<p>2frwの実行結果が上例の1rwと同じになると予測していた方には意外かもしれません。しかし、これは嘘でもバグでもなく<tt class="docutils literal">fread(3)</tt>は何も読み取れません。一般的なシステムでは、まず間違いなくこのような結果になります。</p>
</div>
<div class="section" id="stdiofile">
<h3>stdioの<tt class="docutils literal">FILE</tt>が持つ内部バッファ</h3>
<p>1rwと2frwの実行結果の違いはどこから来るのでしょうか。<tt class="docutils literal">fwrite(3)</tt>が期待通りにファイルへ書き込んでくれない、もしくは<tt class="docutils literal">fread(3)</tt> が期待通りに読み取ってくれないのでしょうか。実はこの挙動の違いはstdioの<tt class="docutils literal">FILE *</tt>が持つ内部バッファの存在がための動作で、仕様通りなのです。</p>
<p><tt class="docutils literal">FILE</tt>は通常<tt class="docutils literal">fopen(3)</tt>によって返され、ユーザプログラム（アプリケーション）は<tt class="docutils literal">FILE</tt>を用いファイルI/Oを行います。では<tt class="docutils literal">FILE</tt>とは何でしょうか？ おっと、GNU Libcはその内容をユーザプログラムには隠蔽しており、単に構造体ポインタであるという以上のことはよく分かりません。マニュアルにも<tt class="docutils literal">FILE</tt>を引数とするライブラリ関数はいくつも記述されていますが、<tt class="docutils literal">FILE</tt>構造体の定義は記述されていません。えぇ。定義の詳細は知らなくとも良いのです。しかし、機能はしっかり把握する必要があります。</p>
<p><tt class="docutils literal">FILE</tt>は、ファイルディスクリプタを用いたファイルI/Oに対し、高水準I/O、ストリームI/Oなどとも呼ばれており、<tt class="docutils literal">printf(3)</tt>に代表される強力なフォーマットI/Oインタフェースは広く使用されています。本記事で注目するのは「<tt class="docutils literal">FILE</tt>構造体は内部にバッファを持つ」という機能です。操作ライブラリとしての<tt class="docutils literal">setvbuf(3)</tt>が重要と言う意味ではなく、内部バッファの存在が上記のような動作の違いを生んでいるのです。</p>
<p>例えば、<tt class="docutils literal">fopen(3)</tt>したファイルへ1から100までの数字を（可読文字列として）書き出すとします。1行に数字1つです。サンプルコードを提示するまでもなく<tt class="docutils literal">fprintf(3)</tt>をforループでまわすコードが一般的でしょう。この時<tt class="docutils literal">fprintf(3)</tt>は<tt class="docutils literal">FILE</tt>が持つ内部バッファへ文字列を付け足すように振る舞います。つまり<tt class="docutils literal">&quot;1\n&quot;</tt>、<tt class="docutils literal">&quot;1\n2\n&quot;</tt>、と成長していき、最終的に<tt class="docutils literal"><span class="pre">&quot;1\n2\n3\n...100\n&quot;</span></tt>という文字列が内部バッファに作成されます。この時点では書き出す内容は内部バッファに留まり、実際のファイルへは書き出されていません（<a class="reference internal" href="#id1">図1</a>）。</p>
<p><tt class="docutils literal">fwrite(3)</tt>や<tt class="docutils literal">fprintf(3)</tt>は毎回<tt class="docutils literal">write(2)</tt>を発行するわけではなく、この場合でも、この時点では<tt class="docutils literal">write(2)</tt>はまだ発行されておらず、ユーザプログラムがメモリ内で<tt class="docutils literal">strcat(3)</tt>などを用い文字列を作成した場合と変りません。</p>
<p>上例の2frw.cでも<tt class="docutils literal">fwrite(3)</tt> は渡されたデータを内部バッファへメモリ間コピーしたに過ぎず、ディスク上のファイルは作成された直後のサイズが0バイトのままです。
このため、<tt class="docutils literal">fread(3)</tt> は1 バイトも読み取れません（<a class="reference internal" href="#id2">図2</a>）。</p>
<p><span class="target" id="id1">図1</span> 内部バッファへ蓄えられる書き込み</p>
<img alt="fig/aio-fig01.png" src="fig/aio-fig01.png" />
<p><span class="target" id="id2">図2</span> FILEを用いた書き込み ― 2frwの動作</p>
<img alt="fig/aio-fig02.png" src="fig/aio-fig02.png" />
<p>対して1rw.cでは<tt class="docutils literal">FILE</tt>を用いないため、内部バッファは存在しません。<tt class="docutils literal">write(2)</tt>を発行し、ファイルシステムへデータを渡します（<a class="reference internal" href="#id3">図3</a>）。</p>
<p><span class="target" id="id3">図3</span> ファイルディスクリプタを用いた書き込み ― 1rwの動作</p>
<img alt="fig/aio-fig03.png" src="fig/aio-fig03.png" />
<p>では、<a class="reference internal" href="#rw-c">1rw.c</a>は前掲のバッファのプログラミング入門に沿わない悪いプログラムで、遅いディスクに引きずられるようにCPUの速度が犠牲になっているのでしょうか。実はそうではありません。<a class="reference internal" href="#rw-c">1rw.c</a>はシステム側で管理される本来のバッファの恩恵を受けており、決して悪いプログラミングではありません。</p>
<p><a class="reference internal" href="#frw-c">2frw.c</a>の動作は仕様通りですが、プログラマの意図とは異なる、アプリケーションバグかもしれません。<a class="reference internal" href="#rw-c">1rw.c</a>と同じ結果にしたい場合は、後述する<tt class="docutils literal">fflush(3)</tt>をコールし、内部バッファを<tt class="docutils literal">write(2)</tt>する必要があります。</p>
<p>これまでは一般に「バッファ」と呼ばれるものを指すのに、「本来のバッファ」、「stdio <tt class="docutils literal">FILE</tt>の内部バッファ」、「単なる作業用のメモリ領域」などと言葉を使い分けてきましたが、次節からはひとつづつ解説します。</p>
</div>
<div class="section" id="id4">
<h3>システムレベルのバッファリング</h3>
<p>CPUに対してディスクの速度が遅いことは広く知られており、ディスクの内容をメモリに保持するバッファリングと言う動作もよく知られています。このバッファは、アプリケーションが直接操作するものではなく、システム側が管理します。アプリケーションがファイルへ書き込んだ場合のシステム動作を、階層構造から簡単におさらいしてみましょう。大雑把に言えば、ファイル名（オープン後はファイルディスクリプタ）と書き込むデータという2つの入力が、ディスク上のブロックに到達するまでの流れです。</p>
<p>基本的には現代のOSに共通する話だと思いますが、筆者はすべてのOSを熟知しているわけではないので :-) 当世のLinuxを前提とします。</p>
<p><span class="target" id="id5">図4</span> ファイルへの書き込み ― バッファキャッシュまで</p>
<img alt="fig/aio-fig04.png" src="fig/aio-fig04.png" />
<dl class="docutils">
<dt>アプリケーション</dt>
<dd>名前によりファイル名を識別し、オープン後はファイルディスクリプタによりファイルを操作する。ファイルディスクリプタと書き込むデータをファイルシステムへ渡す。</dd>
<dt>ファイルシステム</dt>
<dd>アプリケーションから受け取ったファイルディスクリプタをinode(アイ-ノード) へ変換する。ファイル内容（ファイルデータ）を保存するディスクブロックはinodeが保持、管理する。ディスクブロックに対応するメモリ（バッファ）内容を書き換え、そのバッファとinodeがdirtyである（書き込む必要がある）とマークする。</dd>
</dl>
<p>ファイルシステムがバッファとinodeをdirtyにすれば、制御はアプリケーションへ返ります（<a class="reference internal" href="#id5">図4</a>）。つまりこの時点では書き込んだ内容はまだディスクへ到達しておらず、システムが管理するメモリ内に留まっています。ファイルシステムが書き換えたこのメモリが前掲の「本来のバッファ」、すなわちCPUとディスクの速度差の吸収を目的とした緩衝体です。バッファキャッシュとも呼びます。</p>
<p>バッファキャッシュはシステムが管理するもので、ディスクへの実際の書き出しはシステムにより非同期に行われます。すでに制御はアプリケーションへ返っており、別の流れになりますが、データがバッファキャッシュからディスクへ到達するまでの流れを追います（<a class="reference internal" href="#id6">図5</a>）。</p>
<p><span class="target" id="id6">図5</span> ファイルへの書き込み ― バッファキャッシュの書き戻し</p>
<img alt="fig/aio-fig05.png" src="fig/aio-fig05.png" />
<dl class="docutils">
<dt>dirtyバッファキャッシュ書き戻しカーネルスレッド</dt>
<dd>ディスクへ書き込む必要がある（dirty）とされたけれど、メモリ（バッファキャッシュ）内に留まったままにされているデータは、一定時間経過後、または空きメモリが一定量よりも少なくなった時点でブロックデバイスへ渡す。明示的に書き戻しを指示することもできる。</dd>
<dt>ブロックデバイス</dt>
<dd>渡されたデータをディスクなどのデバイスへ書き込み、永続的に保存する。</dd>
</dl>
<p>デバイスが内部に持つバッファキャッシュ（ハードウェアレベルのバッファリング）も存在しますが、本記事では取り上げません。</p>
<p>バッファキャッシュ書き戻し処理は、それまでのpdflushスレッドに代わり、平成21年12月にリリースされたLinux 2.6.32からbdiスレッド（backing_device_information）に置き換えられました。per-bdi flusherとも呼ばれます。役割／位置付けは変りませんが、構造的な変化は大きく、dirtyなinode、バッファを小分けに管理、書き戻すことにより、速度性能改善を目的とした変更です。この方向性はその後も維持され、変更作業は継続されています。</p>
<p>開始の契機など書き戻し処理には<tt class="docutils literal">/proc/sys/vm</tt>下にいくつかのパラメータが用意されています。詳細は割愛しますが、環境に応じたチューニングは一考の価値があります。</p>
<p>取り上げられる機会が少ないようですが、laptop_modeなども有用でしょう。</p>
<p class="footnote">書き戻しの契機は上記以外にもMagic SysRqキーによる緊急remount、緊急syncがあります。</p>
<p>バッファキャッシュ内に留まっているデータは放っておいても、最終的にはディスクへ書き戻されますが、この間に電源断などの事故により正常に書き戻されなければ、ディスク上のファイルシステムは整合性を保てなくなる恐れがあります。</p>
<p>この場合に、ファイルシステムの整合性の確認／修復、また可能ならばファイルデータの救済などを行うのが、通常システム起動時に実行されるfsckです。</p>
<p>ディスクI/O回数を増やしシステムパフォーマンスを犠牲にしてでも、ファイルシステムが不整合になる可能性を減らしたい場合には、明示的に書き戻しを指示する<tt class="docutils literal">fsync(2)</tt>、<tt class="docutils literal">sync(8)</tt>が有効です。ファイルオープン時にO_SYNCを指定しても同等の効果が得られますが、書き込み回数が多い場合には性能劣化が大きくなるため注意が必要です。</p>
<p>Linux固有のシステムコールですが、対象範囲を指定できる<tt class="docutils literal">sync_file_range(2)</tt>というのもあります。</p>
<p>書き戻し後にdirtyからclean（ディスクとメモリの内容が一致しており、もう書き戻す必要がない）になったバッファはそのままメモリ上に残るかもしれませんし、システムが空きメモリを必要としている場合には破棄されるかもしれません。</p>
<p>バッファキャッシュにはシステムの空きメモリを流用します。</p>
<p>空いていればどんどんバッファキャッシュにまわされ、別の用途にメモリが必要になれば使用頻度が低いバッファキャッシュは破棄され、必要な用途に割り当てられます。</p>
<p>破棄されるのは使用頻度が低い順ですが、この破棄とディスクからの再読み取りが頻発してしまうと、システムのパフォーマンス低下の一因となり得ます。</p>
</div>
<div class="section" id="id7">
<h3>I/O単位サイズ</h3>
<p>I/Oでは単位サイズも重要です。アプリケーションが書き込むデータサイズは任意ですが、ファイルシステムでのI/Oはブロック単位、またブロックデバイスへ渡されるのはセクタ単位です。</p>
<p>いずれも512よりも大きい2の累乗が使用されますが、具体的な値は環境に依存します。例えばファイルシステム作成時にブロックサイズを512バイト、1KB、4KB...64KBなどにも指定可能です。デフォルトではファイルシステム容量からブロックサイズを自動的に決定するファイルシステムが多いようです。通常はメモリページサイズがファイルシステムブロックサイズ上限になります。実際のブロックサイズは<tt class="docutils literal">stat(2)</tt>が返す<tt class="docutils literal">st_blksize</tt>で確認できます。</p>
</div>
<div class="section" id="id8">
<h3>アプリケーションレベルのバッファリング</h3>
<p>システムが管理するバッファキャッシュをシステムレベルのバッファリングと呼ぶのに対し、アプリケーションが自身で管理することをアプリケーションレベルのバッファリング、またはユーザレベルのバッファリングと呼びます。stdioの<tt class="docutils literal">FILE</tt>は厳密にはライブラリレベルのバッファリングですが、アプリケーションレベルのバッファリングの一種 です。</p>
<p><a class="reference internal" href="#frw-c">2frw.c</a>を<tt class="docutils literal">FILE</tt>の内部バッファのため期待通りに動作しない例として取り上げたため、印象が悪くなってしまったかもしれませんが、<tt class="docutils literal">FILE</tt>は決して悪いものではなく、それどころか内部バッファが威力を発揮し、性能／効率に大きく貢献する場合が多くあります。例えば小さいサイズのデータを数多く読み書きする場合を考えます。その度に<tt class="docutils literal">read(2)</tt>、<tt class="docutils literal">write(2)</tt>を発行することももちろん可能ですが、システム内部では既定のI/O単位サイズに丸めあげられるため、コスト高になる場合があります。</p>
<p>ファイルの10バイト目だけを変更する場合に、<tt class="docutils literal">write(2)</tt>で1バイトだけ書き込むと、システムはバッファを完成させるため前9バイト、後ろ502バイト（最低でも）を補填します。補填とはつまりファイルを読み取り、バッファ内をファイルデータで満たすことです。ファイルがすでに読み取られており、バッファが存在していれば新たな読み取りは発生しませんが、I/O単位サイズに一致しない書き込みは遅い読み取り動作を待たなければなりません。書き込みをI/O単位サイズに一致させると、バッファ内容を現在のファイル内容で補填する必要がなくなり、読み取りの待ち時間を削減できます。</p>
<p>また、システムコールは一度制御がカーネルへ移るため、多少なりともオーバヘッドを伴います。I/O結果は同じでも、I/O単位サイズに注意し、システムコール発行回数を削減した方が効率面では有利です。このことはddを使って簡単に確認できます。</p>
<p>write(2) のサイズと回数比較例</p>
<div class="highlight"><pre><span style="color: #19177C">$ </span>dd <span style="color: #008000; font-weight: bold">if</span><span style="color: #666666">=</span>/dev/zero <span style="color: #19177C">bs</span><span style="color: #666666">=</span>1M <span style="color: #19177C">count</span><span style="color: #666666">=</span>1 2&gt; /dev/null | <span style="color: #BB6622; font-weight: bold">\</span>
    <span style="color: #008000">time </span>dd <span style="color: #19177C">of</span><span style="color: #666666">=</span>/dev/null <span style="color: #19177C">obs</span><span style="color: #666666">=</span>1
2048+0 records in
1048576+0 records out
1048576 bytes <span style="color: #666666">(</span>1.0 MB<span style="color: #666666">)</span> copied, 0.282584 s, 3.7 MB/s
0.11user 0.16system 0:00.28elapsed 98%CPU <span style="color: #666666">(</span>0avgtext+0avgdata 0maxresident<span style="color: #666666">)</span>k
0inputs+0 outputs <span style="color: #666666">(</span>0 major+211minor<span style="color: #666666">)</span> pagefaults 0swaps

<span style="color: #19177C">$ </span>dd <span style="color: #008000; font-weight: bold">if</span><span style="color: #666666">=</span>/dev/zero <span style="color: #19177C">bs</span><span style="color: #666666">=</span>1M <span style="color: #19177C">count</span><span style="color: #666666">=</span>1 2&gt;/dev/null | <span style="color: #BB6622; font-weight: bold">\</span>
    <span style="color: #008000">time </span>dd <span style="color: #19177C">of</span><span style="color: #666666">=</span>/dev/null <span style="color: #19177C">obs</span><span style="color: #666666">=</span>512
2048+0 records in
2048+0 records out
1048576 bytes <span style="color: #666666">(</span>1.0 MB<span style="color: #666666">)</span> copied, 0.00376508 s, 279 MB/s
0.00user 0.00system 0:00.00elapsed 50%CPU <span style="color: #666666">(</span>0avgtext+0avgdata 0maxresident<span style="color: #666666">)</span>k
0inputs+0 outputs <span style="color: #666666">(</span>0major+213 minor<span style="color: #666666">)</span>pagefaults 0swaps
</pre></div>
<p>単純すぎて比較が見えにくいかもしれませんが、1バイトの書き込みを1M回実行した場合と、512バイトづつの書き込みを2K回実行した場合の資源消費を示した例です。入出力先はディスク上の通常ファイルではないため、実質的に<tt class="docutils literal">write(2)</tt>と若干のメモリ操作だけの比較となります。<tt class="docutils literal">write(2)</tt>を1M回実行した場合よりも2K回実行した場合の方が短時間で終了し、CPU使用率も少ないことがわかります。書き込みサイズを512の倍数にしておけばほぼ同等の効率が得られます。上記の単純比較も付属ソースコード一式に含めてあります。</p>
<p>上例から、小さいサイズの<tt class="docutils literal">write(2)</tt>を多数繰り返すよりも、大きいサイズでまとめて行う方が効率的であることがわかります。stdioの<tt class="docutils literal">FILE</tt>はまさにこの動作をアプリケーションに提供するものです。<tt class="docutils literal">FILE</tt>への書き込みは内部バッファへ蓄積され、一杯になった時、または明示的に掃き出しを要求された場合にのみ、内部バッファを<tt class="docutils literal">write(2)</tt>へ渡します。読み取りの場合も内部バッファサイズで<tt class="docutils literal">read(2)</tt>し、アプリケーションへは内部バッファからデータを返します。</p>
</div>
<div class="section" id="file">
<h3>FILEのバッファフラッシュ</h3>
<p>それでは内部バッファのサイズはいくつで、掃き出し要求は具体的にどう行うのでしょうか。アプリケーションはファイルシステムへデータを渡すのですから、I/O サイズはファイルシステムのブロックサイズの倍数で、かつ大きい方が速度面では有利です。以前は<tt class="docutils literal">/usr/include/stdio.h</tt>で定義されている<tt class="docutils literal">BUFSIZ</tt>がそのサイズだったと思うのですが（あまりにも古い記憶だから自信がありません）、手元のGNU Libc v2.7で確認すると<tt class="docutils literal">fopen(3)</tt>が<tt class="docutils literal">stat(2)</tt>を発行し、得られた<tt class="docutils literal">st_blksize</tt>を内部バッファサイズとしていました（少なくとも通常ファイルの場合は）。前述のように<tt class="docutils literal">st_blksize</tt>はファイルシステムに依存しますから、固定されているわけではなく、対象ファイルに最適な値を内部バッファサイズとしています。アプリケーションのI/O単位では文字、行といった概念が一般的で、ブロックデバイスのセクタは通常意識しません。FILEでの内部バッファリングは、性能劣化を防ぎつつ任意サイズでのI/Oをプログラマに提供するものとも言えます。内部バッファリングのレベルも指定可能です。全バッファリング（fully buffered）、行バッファリング（line buffered）、バッファリングなし（unbuffered）の三段階が提供されており、デフォルトは全バッファリングとされており、内部バッファが一杯になると内部で<tt class="docutils literal">write(2)</tt>を発行して書き出しますが、出力先が端末の場合はデフォルトで行バッファリングとされます。このため改行コードで終わる文字列を標準出力（通常は端末画面）に書き込むと、バッファリングされず即座に表示されます。バッファリングのサイズ、動作の変更詳細については、<tt class="docutils literal">setvbuf(3)</tt>を参照してください。</p>
</div>
<div class="section" id="id9">
<h3>FILEの書き込みエラー</h3>
<p>内部バッファが一杯でなくとも明示的に書き出す場合は、<tt class="docutils literal">fflush(3)</tt>をコールします。見落とされがちですがこの戻り値は重要です。<tt class="docutils literal">write(2)</tt>の戻り値は、例えばファイルシステムの容量不足が原因で書き込めなかったなどのエラーを期待するため、あまり見落とされることはありません。<tt class="docutils literal">fwrite(3)</tt>、<tt class="docutils literal">fprintf(3)</tt>の戻り値も、<tt class="docutils literal">write(2)</tt>のようなエラーが期待されるようで、これもあまり見落とされることはないようです。しかし、<tt class="docutils literal">fwrite(3)</tt>は内部バッファへ書き込んだだけで、<tt class="docutils literal">write(2)</tt>を発行しない場合が多くあります。このため、<tt class="docutils literal">ENOSPC</tt>などのエラーを正しく検知できるかは期待できません。エラーを調べるには<tt class="docutils literal">fflush(3)</tt>の戻り値が重要です。<tt class="docutils literal">fwrite(3)</tt>の戻り値にももちろん意味はありますので、両方とも確認すべきでしょう。<tt class="docutils literal">ferror(3)</tt>も有効です。さらに<tt class="docutils literal">fflush(3)</tt>をコールしてもデータがディスクへ確実に保存されたとは限りません。実質的に<tt class="docutils literal">write(2)</tt>に相当するものであり、データはバッファキャッシュへ留まります。ディスクへ到達させるには前述のように<tt class="docutils literal">fsync(2)</tt>を発行する必要があります。</p>
<p>また<tt class="docutils literal">FILE</tt>クローズ時にも内部バッファは掃き出されるため、<tt class="docutils literal">fclose(3)</tt>の戻り値も重要です。同様に見落とされがちですが、<tt class="docutils literal">fwrite(3)</tt>の戻り値は確認するけれど、<tt class="docutils literal">fclose(3)</tt>の戻り値を確認しないのは、アプリケーションバグです。だいぶ昔の話ですが、<tt class="docutils literal">FILE</tt>を用いた実装のメールクライアントがメールを受信し、ユーザのホームディレクトリへ書き込んだのは良いけれど、<tt class="docutils literal">fclose(3)</tt>の戻り値を確認しなかったため、ホームディレクトリが一杯になっていることがわからず、その時に受信したメールをすべて失ってしまい、著者の周囲で悲鳴が上がったことがあります。2frw.cのようなプログラムが、ライフラインと呼ばれることもあるメールを扱うならば、<tt class="docutils literal">fflush(3)</tt>や<tt class="docutils literal">fsync(2)</tt>も発行した方が良いでしょう。同様に見落とされがちですが、<tt class="docutils literal">FILE</tt>を使用せず<tt class="docutils literal">write(2)</tt>、<tt class="docutils literal">close(2)</tt>する場合でも<tt class="docutils literal">close(2)</tt>の戻り値確認は重要です。<tt class="docutils literal">write(2)</tt>ではエラーを返さず<tt class="docutils literal">close(2)</tt>で初めてエラーを検知できるファイルシステムもあります。実装にもよりますが、NFSなどがそれにあたります。</p>
<p>初めに挙げた「単なる作業用のメモリ領域」は変数名こそ<tt class="docutils literal">buffer</tt>と名乗っていますが、動作的にはバッファとは言い難く、強いて言えばアプリケーションレベルのバッファリングと呼べなくもないという程度のものです。</p>
</div>
<div class="section" id="id10">
<h3>シーケンシャルアクセスの効率化</h3>
<p>ファイルシステムではブロック単位、ブロックデバイスではセクタ単位でI/Oが発行されるのは前述の通りですが、このことはアプリケーションがファイルからデータを1バイト読み取るだけでも、システム内部ではもっと多くのバイト数が読み取られことを意味します。さらに1ブロックだけが必要とされる場合でも、自動的にその続きの数ブロックも同時に読み取り、本来要求されたI/Oを阻害しない範囲でバッファキャッシュへ蓄えておく先読み（read-ahead）という機能もあります。この機能はディスクのヘッドシーク時間を考えると非常に効果的です。さらにLinuxではアプリケーションのファイルアクセスパターンに応じて動的に先読み量を変化させ、シーケンシャルアクセスの場合には自動的に先読み量を増加させて、より多くのデータを先読みします。</p>
<p>初めからシーケンシャルにアクセスすると決まっているアプリケーションならば、事前にシステムに通知することにより、先読み効果を最大限に活かせます。この通知には<tt class="docutils literal">madvise(2)</tt>、<tt class="docutils literal">fadvise(2)</tt>、<tt class="docutils literal">readahead(2)</tt>などを用います。シーケンシャルアクセス以外の場合でもその用途を通知することで、効率向上が図れます。書き込みの場合でもディスクブロック割り当てをあらかじめ要求する<tt class="docutils literal">fallocate(2)</tt>というのもあります。詳細は各システムコールのマニュアルを参照してください。</p>
<p>先読みを活用しても、読み取るデータが常にバッファキャッシュ内に存在するとは限りません。システムがメモリ不足になった場合はやはり破棄される恐れがあります。後述の<tt class="docutils literal">mmap(2)</tt>に加え、<tt class="docutils literal">mlock(2)</tt>も用いると、プロセスアドレス空間をメモリ上に保つことができ、読み取るデータをバッファキャッシュ内に保持できます。もちろん、これはメモリ消費とのトレードオフです。</p>
<p>ここまで 「バッファ」という言葉に解説を加えました。次回ではバッファキャッシュを意識したさまざまなファイルI/Oを解説します。</p>
<p>（初出: H22/4）</p>
<p>Copyright © 2010 SENJU Jiro</p>
<p class="footnote">本記事のサンプルコードは、以下のリンクよりダウンロードすることができます。記事と合わせてご参照ください。<br />
[<a href="http://www.oreilly.co.jp/pub/pg_high/ph20100730.tar.bz2">サンプルコード</a>]<br />
また、本稿で取り上げた内容は、記事の執筆時の情報に基づいており、その後の実装の進展、変更等により、現在の情報とは異なる場合があります。<br />
本連載「プログラマーズ・ハイ」では読者からのご意見、ご感想を切望しています。今後取り上げるテーマなどについてもご意見をお寄せください。</p>
</div>
]]>
    </content>
</entry>

<entry>
    <title>epollインタフェースとsignalfd（2）</title>
    <link rel="alternate" type="text/html" href="http://www.oreilly.co.jp/community/blog/2010/06/epoll-interface-and-signalfd-part2.html" />
    <id>tag:www.oreilly.co.jp,2010:/community/blog//4.622</id>

    <published>2010-06-15T06:32:21Z</published>
    <updated>2010-12-08T09:36:11Z</updated>

    <summary><![CDATA[ &gt;&gt; (1)よりつづく 本記事のサンプルコードは、以下のリンクよりダウンロードすることができます。記事と合わせてご参照ください。 [ サンプルコード ] 子プロセスの同期/非同期 4ep...]]></summary>
    <author>
        <name>千住治郎</name>
        
    </author>
    
        <category term="Programmer&apos;s High" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="ja" xml:base="http://www.oreilly.co.jp/community/blog/">
        <![CDATA[<img alt="http://www.oreilly.co.jp/community/blog/images/union/pg_high_logo.png" src="http://www.oreilly.co.jp/community/blog/images/union/pg_high_logo.png" />
<p class="lead">&gt;&gt;<a href="http://www.oreilly.co.jp/community/blog/2010/05/epoll-interface-and-signalfd-part1.html"> (1)よりつづく</a></p>
<p class="footnote docutils">
本記事のサンプルコードは、以下のリンクよりダウンロードすることができます。記事と合わせてご参照ください。<br />
[ <a class="reference external" href="http://www.oreilly.co.jp/pub/pg_high/ph20100223.tar.bz2">サンプルコード</a> ]</p>
<h2>子プロセスの同期/非同期</h2>
<p>4epoll、5epoll-multiへのダミー処理追加には、いくつかの注意点があります。1baseと同じように、epollによるイベントループがその場で子プロセスの終了を待つようにすると、1つのダミー処理の終了を他のセッションが待つことになってしまいます。
これはepollによる、イベントループのI/Oの多重性を損なう大きな問題です（ <a class="reference internal" href="#id16">図1.9</a> ）。</p>
<p><span class="target" id="id16">図1.9</span> epoll の多重性を損ねるダミー処理追加</p>
<img alt="epoll2-fig9.png" src="http://www.oreilly.co.jp/community/blog/2010/06/fig/epoll2-fig9.png" />
<p>（セッションCは割愛）</p>
]]>
        <![CDATA[<p>この問題はマルチスレッドを用いず、シングルスレッドで自らI/Oを多重化する構造に由来します。</p>
<p>epollによるイベントループを用いた構造では、子プロセスの終了を同期的に待ち合わせることはせず、親プロセスに対し送信されるSIGCHLDシグナルにより、子プロセスの終了を判断するようにし、 <a class="reference internal" href="http://www.oreilly.co.jp/community/blog/2010/05/fig/epoll2-fig8.png">図1.8</a> と同様のインタリーブを実現する必要があります。</p>
<p>プロセス管理一般の話になりますが、親プロセスは通常、 <tt class="docutils literal">wait(2)</tt> （またはそのファミリ）を発行し、子プロセスがまだ占有しているシステム資源を解放します。 <tt class="docutils literal">wait(2)</tt> はたびたび誤解されることがあり、その機能は子プロセスの終了を「待つ」ことだけではありません。同期的に <tt class="docutils literal">wait(2)</tt> を使用する場合は、待つことが主な目的であることが多く、実際何も問題はありませんが、終了したプロセスはシステム内から完全に居なくなるわけではなく、「ゾンビ」という状態で居座り続けます。一切動作せずプロセス内で使用した資源も解放済みですが、存在している以上、プロセスとして体をなす最低限のシステム資源を確保し続けます。このゾンビプロセスを完全に廃棄し、システム資源を解放するのも <tt class="docutils literal">wait(2)</tt> の重要な機能です。言い換えるとその場で子プロセスの終了を待ち合わせない非同期の場合でも、 <tt class="docutils literal">wait(2)</tt> は発行しなければなりません。</p>
<p class="footnote">子プロセスをゾンビにさせない方法もありますが、本記事では取り扱いません。</p>
<p>epoll によるイベントループでは子プロセスの終了をその場では待ってはいけませんが、 <tt class="docutils literal">wait(2)</tt> の発行を省略することもできません。省略するとシステムがゾンビプロセスだらけになってしまい、最後には新規プロセスを起動出来なくなってしまうでしょう。</p>

<p class="footnote">
少し話が逸れますが、現実には <tt class="docutils literal">wait(2)</tt> が発行されない子プロセスが発生することは容易にあり得ます。プログラムミスもあるでしょうし、親プロセスが <tt class="docutils literal">wait(2)</tt> する前に強制的に終了させられたなどは普通に起こり得ます。このためシステム側にもゾンビプロセス対策（対策と言っても特別のものではなく、通常機能の範囲です）があり、先に親プロセスが終了してしまった子プロセスは、強制的に <tt class="docutils literal">init</tt> プロセスの子とし、 <tt class="docutils literal">init</tt> が <tt class="docutils literal">wait(2)</tt> を発行し、ゾンビプロセスを廃棄します。</p>
<p>サンプルプログラムではダミー処理用に子プロセスを起動しますが、epollによるイベントループはその終了を非同期に検知し、 <tt class="docutils literal">wait(2)</tt> を発行し、システムからゾンビプロセスを破棄することにします。この検知にはシグナルを用い、前記事でも簡単に触れておいた <tt class="docutils literal">signalfd(2)</tt> を使用します。</p>
<div class="section" id="signalfd">
<h3>signalfd</h3>
<p>子プロセスが終了すると親プロセスにはシグナルSIGCHLDが送られます。 <tt class="docutils literal">sigaction(2)</tt> によりシグナルハンドラを登録するのが従来より一般的な処理方法ですが、本記事ではepoll同様、Linuxに比較的新しく実装された <tt class="docutils literal">signalfd(2)</tt> を用い、epollによるイベントループ内でシグナルを処理してみます。</p>
<p class="footnote">epoll インタフェースには <tt class="docutils literal">select(2)</tt> / <tt class="docutils literal">pselect(2)</tt> 、 <tt class="docutils literal">poll(2)</tt> / <tt class="docutils literal">ppoll(2)</tt> の関係同様に、シグナルマスクを指定できる <tt class="docutils literal">epoll_pwait(2)</tt> も用意されていますが、本記事では取り上げません。</p>
<p><tt class="docutils literal">signalfd(2)</tt> はファイルの <tt class="docutils literal">open(2)</tt> 同様に、ファイルディスクリプタを返します。このファイルディスクリプタを <tt class="docutils literal">read(2)</tt> すると、シグナルが届いていれば、そのシグナルに関する詳細な情報を読み取れます。届いていなければ、届くまで <tt class="docutils literal">read(2)</tt> は待ち続けます（ブロックする）。epollによるイベントループでsignalfdのファイルディスクリプタを見張り、読み取り可能を検知した時がシグナルが届いた時です。ここで <tt class="docutils literal">wait(2)</tt> を発行すれば、ブロックすることはありませんし、必ず終了した子プロセスのプロセスIDが得られます。</p>
<p>しかし、この方式には注意が必要です。現代のシグナルはシステム内部で「保留」（suspend）されることがあります。シグナルハンドラは通常単純でごく短時間で完了する処理しか行いませんが（多くの処理をすべきではない）、この間に同じシグナルがもう1つ届いた場合には、システム側で保留し、シグナルハンドラ完了後に改めて届けてくれます。</p>
<p>この動作はシグナルを失わず信頼性を高めるものですが、すでにシグナルを保留している時に、さらにもう1つ同じシグナルが発生すると、保留せずに破棄します。つまり（シグナル種類ごとに）複数は保留しないという動作です。</p>
<p>一般にシグナルの宛先はプロセスでもスレッドでも良いのですが、2pthread-unlimited、3pthread-poolでは子プロセスを起動するのがスレッドのため、SIGCHLDはスレッドに届けられます。また同期的に待ち合わせるため保留されることもありません。</p>
<p>しかし、4epollはシングルスレッドで複数の子プロセスを起動します。さらにシグナルハンドラを用いず、signalfdをイベントループで見張るため、発生したSIGCHLDはすぐには処理されず、システム内でまず保留されます。多くのセッションを同時に処理する状態では、続くSIGCHLDが破棄されてしまうことはまず間違いないでしょう。</p>
<p class="footnote">SIGCHLDは標準シグナルという種類ですが、リアルタイムシグナルという種類はすでに保留済みのシグナルが新たに発生しても破棄されず、順序も維持したまま届けられます。</p>
<p>シグナルの保留、破棄は理屈の上ではいつでもどこでも起こり得るものですが、通常のシグナルハンドラを登録する方式を用い、ハンドラ内の処理も少なければ、破棄される機会はずっと少なくなり、実際にはそれほどお目にかからないと思われます。言い換えるとsignalfdを見張るイベントループでは当り前のように破棄されてしまいます。</p>
<p>注意は必要ですが、この点については、「signalfdが読み取り可能になった時には <tt class="docutils literal">wait(2)</tt> すべきゾンビプロセスが複数存在するかもしれない」ということを意識すれば、対応は困難ではありません。</p>
<p>epollでsignalfdを見張る場合の注意点はもう1つあります。signalfdから読み取れる情報（siginfo）は必ず読み出さなければならないことです。従来の <tt class="docutils literal">sigaction(2)</tt> で登録するシグナルハンドラのパラメータを考えるとわかります。 <tt class="docutils literal">struct sigaction</tt> の <tt class="docutils literal">sa_handler</tt> ではなく、 <tt class="docutils literal">sa_sigaction</tt> の方です。パラメータには <tt class="docutils literal">siginfo_t *</tt> があります。この情報はシステム側が領域を確保し、シグナルハンドラ実行後に破棄されるものです。</p>
<p>epollによるイベントループではシグナルハンドラを使用しないため、この情報はシステム側に留まったままとなり、これを参照可能にすると同時に破棄するのがsignalfdからの読み取りです。signalfdから情報を読み出してやらないと情報がシステムが領域を解放できず、また当然ながらepollがいつまでも読み取り可能を検知し続けてしまいます。</p>
<p>サンプルプログラムの改造では、signalfdが読み取り可能になる度に、signalfdからの読み出しを一度、その後 <tt class="docutils literal">wait(2)</tt> を（その時点の）ゾンビプロセスが居なくなるまで何度でも実行することとします。</p>
</div>
<div class="section" id="id17">
<h3>セッションソケットの状態遷移</h3>
<p>以上の注意点を踏まえ、4epoll.cのどこへどのようにダミー処理を追加するかを考えます。epollによるイベントループでは、セッションソケットはもともと次のように処理されていました。</p>
<blockquote>
<ol class="arabic simple">
<li>acceptしたソケットをepoll対象へ加える</li>
<li>読み取り可能（HTTPリクエストが届いた）かを見張る</li>
<li>読み取り可能になった</li>
</ol>
<blockquote>
<ul class="simple">
<li>HTTPリクエストを読み取り、</li>
<li>対象ファイルパスを確認する</li>
</ul>
</blockquote>
<ol class="arabic simple" start="4">
<li>書き込み可能（送信できるか）を見張る</li>
<li>書き込み可能になった</li>
</ol>
<blockquote>
<ul class="simple">
<li>対象ファイルを送信する</li>
</ul>
</blockquote>
<ol class="arabic simple" start="6">
<li>ソケットをepoll対象から外しcloseする</li>
</ol>
</blockquote>
<p>ダミー処理の追加位置は、リクエスト読み取り後、かつファイル送信前ですが、その間にイベントループが回ります。セッションソケットはすでにepoll対象に含まれており、そのままではepollがI/O可能かどうかを検知して、ダミー処理完了前にイベントループが処理してしまいます。このため、一時的にepoll対象から外すように変更します。</p>
<p>関連して、epoll に指定していたエッジトリガをレベルトリガへ戻します。</p>
<blockquote>
<ol class="arabic simple">
<li>signalfdを見張る</li>
<li>acceptしたソケットをepoll対象へ加える</li>
<li>読み取り可能（HTTPリクエストが届いた）かを見張る</li>
<li>読み取り可能になった。</li>
</ol>
<blockquote>
<ul class="simple">
<li>HTTPリクエストを読み取り</li>
<li>対象ファイルパスを確認する</li>
<li>セッションソケットをepoll対象から外す</li>
<li>forkしダミー処理実行</li>
</ul>
</blockquote>
<ol class="arabic simple" start="5">
<li>SIGCHLDが到着し、signalfdが読み取り可能になった</li>
</ol>
<blockquote>
<ul class="simple">
<li>セッションソケットを再びepoll対象へ加える</li>
</ul>
</blockquote>
<ol class="arabic simple" start="6">
<li>書き込み可能 (送信できるか) を見張る</li>
<li>書き込み可能になった</li>
</ol>
<blockquote>
<ul class="simple">
<li>対象ファイルを送信する</li>
</ul>
</blockquote>
<ol class="arabic simple" start="8">
<li>ソケットをepoll対象から外しcloseする</li>
</ol>
</blockquote>
</div>
<div class="section" id="id18">
<h3>ソースコードの変更 (2)</h3>
<p>4epoll.cへのダミー処理追加の内容が決定できました。いよいよ変更します。まず、新規関数 <tt class="docutils literal">dummy_wait()</tt> を追加します。内容はsignalfdの読み出し、ゾンビプロセスの破棄、ソケットセッションの復帰です。</p>
<p>前述の 1base.cでは同期的に子プロセスの終了を待つため、 <tt class="docutils literal">waitpid(2)</tt> の <tt class="docutils literal">option</tt> には 0を渡していましたが、こちらではいくつあるかわからない子プロセスを繰り返し <tt class="docutils literal">waitpid(2)</tt> するため、 <tt class="docutils literal">WNOHANG</tt> を渡します。</p>
<p>厳密には、 <tt class="docutils literal">dummy_wait()</tt> が <tt class="docutils literal">wait(2)</tt> してもすでにゾンビプロセスが居なくなっている場合があり得ます。これは前回の <tt class="docutils literal">dummy_wait()</tt> コールですべてのゾンビプロセスを破棄している際中にSIGCHLDが発生した場合に起こります。</p>
<p><tt class="docutils literal">wait(2)</tt> 発行はゾンビプロセスが居なくなるまで繰り返されますから、ループ中に終了した子プロセスはすぐに <tt class="docutils literal">wait(2)</tt> されますが、siginfoはシステム内に保留されたままになります。これはsignalfdからの読み出しと複数回の <tt class="docutils literal">wait(2)</tt> の順序に起因する状態で、余分な <tt class="docutils literal">wait(2)</tt> 以外に問題はなく、実害はありません。</p>
<p>仮に複数回の <tt class="docutils literal">wait(2)</tt> の後でsignalfdを読み出すようにすると、子プロセスの終了を検知できなくなる恐れがあります。</p>
<p>dummy_wait()要約</p>
<pre class="literal-block">
/* 子プロセスとセッションソケットの対応 */
int sock_child [];

dummy_wait(sigfd, epfd)
{
    /* siginfo 読み出し */
    read(sigfd);

    ev.events = EPOLLOUT;
    while (1) {
        /* 子プロセスの破棄 */
        pid = waitpid (pid, &amp;status, WNOHANG);
        if (pid &lt;= 0)
            break ;

        /* 子プロセスに対応するソケットを再び見張る */
        ev.data.fd = sock_child [ pid ];
        epoll_ctl(epfd, EPOLL_CTL_ADD, ev.data.fd, &amp;ev);
    ｝
}
</pre>
<p>また、前述の <tt class="docutils literal">dummy_busy()</tt> のイベントループ対応も加えます。</p>
<p>dummy_busy()のイベントループ対応要約 (2)</p>
<pre class="literal-block">
dummy_busy(sock, do_wait)
{
    pid = fork();

    if (pid &gt; 0) {
        /* 親プロセス */
+       if (!do_wait)
+           /* イベントループの場合 */
+           /* セッションと子プロセスの対応を記録する */
+           /* 子プロセスの終了を待ち合わせずリターンする */
+           sock_child [ pid ] = sock ;
        else
            /* 子プロセスの終了待ち合わせ */
            waitpid (pid, &amp;status, 0);
    } else

        /* 子プロセス --- ダミー処理 */
        exec &quot;find /tmp &gt; /dev/null 2&gt;&amp;1&quot;;
}
</pre>
<p>4epoll.c では <tt class="docutils literal">signalfd(2)</tt> の発行とイベントループへの追加、および <tt class="docutils literal">dummy_wait()</tt> コールを追加します。セッションソケットのepoll対象から外し、ダミー処理を起動するのは <tt class="docutils literal">do_read()</tt> です。</p>
<p>4epoll.c へのダミー処理追加要約</p>
<pre class="literal-block">
do_read(sock, epfd)
{
    read(); /* HTTPリクエスト読み取り */
+   /* このソケットはもう見張る必要がない */
+   epoll_ctl(epfd, EPOLL_CTL_DEL, sock, NULL);

+   dummy_busy(sock, /* do_wait */0);
}

main ()
{
    /* リスニングポートへのデータ到着を見張る */
    epfd = epoll_create(1);
    ev.events = EPOLLIN;
    ev.data.fd = lsock;
    epoll_ctl(epfd, EPOLL_CTL_ADD, lsock, &amp;ev);

+   /* SIGCHLDを見張る */
+   sigemptyset(&amp;mask);
+   sigaddset (&amp;mask, SIGCHLD);
+   sigprocmask(SIG_BLOCK, &amp;mask, NULL);
+   sigfd = signalfd (-1, &amp;mask);
+   ev.events = EPOLLIN;
+   ev.data.fd = sigfd ;
+   epoll_ctl(epfd, EPOLL_CTL_ADD, sigfd, &amp;ev);

    while (1) {
        struct epoll_event e[];
        /* I/O可能なソケットを処理する */
        nfd = epoll_wait(epfd, e);
        for (i = 0; i &lt; nfd; i++) {
            sock = e[i].data.fd ;
            if (sock == lsock)
                do_accept(sock, epfd);
+           else if (sock == sigfd)
+               dummy_wait(sigfd, epfd);
            else if (e[i].events &amp;EPOLLIN)
                do_read (sock, epfd);
            else if (e[i].events &amp; EPOLLOUT )
                do_sfc( sock, epfd );
        }
    }
}
</pre>
<p>5epoll-multi.cへのダミー処理追加は4epoll.cと基本的に変りません。</p>
<p>ただし、今回は測定環境がデュアルコアであり、4epoll.cと差をつけるために子プロセス数はCPU数に一致させました（前回は起動する4epoll相当の子プロセスをCPU数-1としていた)。上記の変更を加えたサンプルプログラムは、付属ソースコード一式に含めてあります。</p>
</div>
</div>
<div class="section" id="id19">
<h2>測定/比較</h2>
<p>ダミー処理を追加し、いよいよサンプルプログラムへ負荷を加え測定してみます。負荷生成/測定プログラムには比較的古典的な <tt class="docutils literal">httperf</tt> を用い、同一ファイルをHTTP GETで連続して要求し、HTTPクライアントから見た応答性（1TCPコネクションの開始から終了まで）に注目します。また、HTTPサーバ側では同時に <tt class="docutils literal">vmstat</tt> を起動し、HTTPサーバとして動作するサンプルプログラムの資源消費に注目します。付属ソースコード一式にはこの測定方法と結果も含めてあります。</p>
<div class="section" id="id20">
<h3>■測定環境</h3>
<blockquote>
<ul class="simple">
<li>一秒間の要求数を指定し、十秒間動作させる</li>
<li>要求するファイルサイズは 4KB</li>
<li>HTTP サーバ役
- Core2 Duo(x86_64)、3GHz
- 4GB
- plain linux-2.6.32.7</li>
<li>HTTP クライアント役は二台
- Celeron、3GHz
- Pentium4、1.8GHz</li>
<li>100M LAN</li>
</ul>
</blockquote>
</div>
<div class="section" id="id21">
<h3>■測定結果の表</h3>
<blockquote>
<ul class="simple">
<li>最左列はHTTPクライアント役1台あたりの秒間要求数。2台あるのでHTTP サーバ役マシンに届く接続要求数はこの2倍</li>
<li><tt class="docutils literal">vmstat</tt> の列はサンプルプログラムと同時に HTTPサーバ役マシン上で実行した <tt class="docutils literal">vmstat</tt> により二秒間隔で採取したシステム資源の消費状況からの最大値</li>
<li><tt class="docutils literal">httperf</tt> の列は測定プログラムの出力結果そのまま。標準偏差（stdev）はバラツキの大きさを表す。例えば、算術平均（avg）が1551.7msecと遅く見えても、半数のセッションは123.5msec（中央値、med）以内で完了しており、それほど遅くはない。この場合の標準偏差は35882.2と大きく、算術平均はごく少数の異常に突出した値により悪い方にひきずられたように見える。</li>
</ul>
</blockquote>
</div>
<div class="section" id="id22">
<h3>1base</h3>
<p>表1.2 測定結果―1base</p>
<table border="1" class="docutils">
<colgroup>
<col width="8%" />
<col width="11%" />
<col width="9%" />
<col width="8%" />
<col width="8%" />
<col width="12%" />
<col width="14%" />
<col width="12%" />
<col width="12%" />
<col width="8%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head" rowspan="3">&nbsp;</th>
<th class="head" colspan="3">vmstat</th>
<th class="head" colspan="6">httperf</th>
</tr>
<tr><th class="head" rowspan="2">procs
(r+b)</th>
<th class="head" rowspan="2">mem
(KB)</th>
<th class="head" rowspan="2">CPU
(%)</th>
<th class="head" colspan="6">応答（msec）</th>
</tr>
<tr><th class="head">min</th>
<th class="head">avg</th>
<th class="head">max</th>
<th class="head">med</th>
<th class="head">stddev</th>
<th class="head">err</th>
</tr>
</thead>
<tbody valign="top">
<tr><td rowspan="2">200</td>
<td rowspan="2">1</td>
<td rowspan="2">2512</td>
<td rowspan="2">43</td>
<td>2.4</td>
<td>7.4</td>
<td>47.7</td>
<td>4.5</td>
<td>7.3</td>
<td>0</td>
</tr>
<tr><td>2.4</td>
<td>8.1</td>
<td>46.5</td>
<td>5.5</td>
<td>7.8</td>
<td>0</td>
</tr>
<tr><td rowspan="2">250</td>
<td rowspan="2">2</td>
<td rowspan="2">2824</td>
<td rowspan="2">51</td>
<td>2.4</td>
<td>76.0</td>
<td>3041.3</td>
<td>34.5</td>
<td>301.1</td>
<td>0</td>
</tr>
<tr><td>2.5</td>
<td>73.1</td>
<td>3040.6</td>
<td>36.5</td>
<td>277.2</td>
<td>0</td>
</tr>
<tr><td rowspan="2">300</td>
<td rowspan="2">1</td>
<td rowspan="2">3188</td>
<td rowspan="2">52</td>
<td>2.5</td>
<td>1162.7</td>
<td>11149.6</td>
<td>123.5</td>
<td>2488.1</td>
<td>64</td>
</tr>
<tr><td>8.7</td>
<td>1551.7</td>
<td>21005.7</td>
<td>123.5</td>
<td>3588.2</td>
<td>0</td>
</tr>
</tbody>
</table>
<p>二台のHTTPクライアントそれぞれが秒間200件のHTTP GETを要求しても、1baseは充分に動作します。</p>
<p>CPU使用率は40%を越える程度で、メモリ消費もほとんど増加しません（シングルスレッド、逐次処理なので当然ですが）。応答性能も良好ですべて大半が数msec以内、最長でも数十msecで完了します。</p>
<p>負荷を秒間250件ずつ（クライアント役は二台なので、サーバ役に届くのは秒間500件）に増やすと、CPU使用率が50%に達し、応答にバラツキが出始めます。</p>
<p>シングルスレッドをデュアルコアシステムで動作させているので、CPU的には1つの限界にほぼ達したと言える状況です（厳密には人為的に加えたダミー処理が <tt class="docutils literal">fork(2)</tt> しているため、CPU使用率が50%を越えてもおかしくありません）。大半のものは数十msec以内で終えていますが、最長で数秒もかかるものが発生しています（バラツキが大きい）。</p>
<p>負荷をさらに増やすと（秒間300件ずつ）この傾向が強まり、バラツキが拡がり、最長秒数も伸び、HTTPクライアントから見てエラーとなるセッションが発生し始めていますが、大半のものはまだ現実的な時間で応答を返しています。秒間250件ずつの場合と比較すると、増加した負荷に対しまだ余裕がある資源 (CPU、メモリ) を使いこなせておらず、応答性能の悪化だけに拍車がかかる状況が見て取れます。サーバ性能としてはすでに頭打ちと言えるでしょう。</p>
</div>
<div class="section" id="id23">
<h3>2pthread-unlimited</h3>
<p>表1.3 測定結果―2pthread-unlimited</p>
<table border="1" class="docutils">
<colgroup>
<col width="7%" />
<col width="12%" />
<col width="10%" />
<col width="7%" />
<col width="7%" />
<col width="12%" />
<col width="13%" />
<col width="12%" />
<col width="12%" />
<col width="7%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head" rowspan="3">&nbsp;</th>
<th class="head" colspan="3">vmstat</th>
<th class="head" colspan="6">httperf</th>
</tr>
<tr><th class="head" rowspan="2">procs
(r+b)</th>
<th class="head" rowspan="2">mem
(KB)</th>
<th class="head" rowspan="2">CPU
(%)</th>
<th class="head" colspan="6">応答（msec）</th>
</tr>
<tr><th class="head">min</th>
<th class="head">avg</th>
<th class="head">max</th>
<th class="head">med</th>
<th class="head">stddev</th>
<th class="head">err</th>
</tr>
</thead>
<tbody valign="top">
<tr><td rowspan="2">200</td>
<td rowspan="2">5</td>
<td rowspan="2">2932</td>
<td rowspan="2">53</td>
<td>2.6</td>
<td>3.8</td>
<td>12.7</td>
<td>3.5</td>
<td>1.3</td>
<td>0</td>
</tr>
<tr><td>2.6</td>
<td>3.8</td>
<td>13.6</td>
<td>3.5</td>
<td>1.3</td>
<td>0</td>
</tr>
<tr><td rowspan="2">250</td>
<td rowspan="2">3</td>
<td rowspan="2">3344</td>
<td rowspan="2">66</td>
<td>2.6</td>
<td>4.6</td>
<td>20.3</td>
<td>3.5</td>
<td>2.3</td>
<td>0</td>
</tr>
<tr><td>2.6</td>
<td>4.8</td>
<td>22.2</td>
<td>4.5</td>
<td>2.5</td>
<td>0</td>
</tr>
<tr><td rowspan="2">300</td>
<td rowspan="2">153</td>
<td rowspan="2">100740</td>
<td rowspan="2">100</td>
<td>2.6</td>
<td>2061.1</td>
<td>12984.4</td>
<td>907.5</td>
<td>2817.4</td>
<td>0</td>
</tr>
<tr><td>2.7</td>
<td>2080.5</td>
<td>21001.2</td>
<td>919.5</td>
<td>2825.1</td>
<td>0</td>
</tr>
</tbody>
</table>
<p>並列性が高いと言えば聞こえは良いかもしれませんが、負荷に比例しCPU/メモリとも多く消費します。応答性能は良好で、バラツキも少なく、秒間250件ずつまででは高速に動作しています。</p>
<p>秒間300件ずつ負荷を増やすと、スレッドを作成してもスケジューリングが間に合わず、 <tt class="docutils literal">vmstat</tt> のprocsの数字が150を越えています。CPU上で実行される順番を待っているスレッド（およびダミーの子プロセス）が多数存在する状態で、CPU使用率は100%に達し、消費メモリも極端に増加しています。</p>
<p>これは1つの限界に達している状態であり、クライアントから見た応答性能も極端に悪化し、平均値でも2秒を越え、最長では20秒にも達し、破綻とも言える状況です。HTTPクライアントにエラーこそ返されませんが、1つの限界に達していると言えます。</p>
</div>
<div class="section" id="id24">
<h3>3pthread-pool</h3>
<p>表1.4 測定結果―3pthread-pool</p>
<table border="1" class="docutils">
<colgroup>
<col width="7%" />
<col width="12%" />
<col width="10%" />
<col width="7%" />
<col width="7%" />
<col width="12%" />
<col width="13%" />
<col width="12%" />
<col width="12%" />
<col width="7%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head" rowspan="3">&nbsp;</th>
<th class="head" colspan="3">vmstat</th>
<th class="head" colspan="6">httperf</th>
</tr>
<tr><th class="head" rowspan="2">procs
(r+b)</th>
<th class="head" rowspan="2">mem
(KB)</th>
<th class="head" rowspan="2">CPU
(%)</th>
<th class="head" colspan="6">応答（msec）</th>
</tr>
<tr><th class="head">min</th>
<th class="head">avg</th>
<th class="head">max</th>
<th class="head">med</th>
<th class="head">stddev</th>
<th class="head">err</th>
</tr>
</thead>
<tbody valign="top">
<tr><td rowspan="2">350</td>
<td rowspan="2">2</td>
<td rowspan="2">3188</td>
<td rowspan="2">79</td>
<td>2.5</td>
<td>6.1</td>
<td>30.7</td>
<td>4.5</td>
<td>4.8</td>
<td>0</td>
</tr>
<tr><td>2.5</td>
<td>6.3</td>
<td>30.2</td>
<td>4.5</td>
<td>5.0</td>
<td>0</td>
</tr>
<tr><td rowspan="2">400</td>
<td rowspan="2">2</td>
<td rowspan="2">3600</td>
<td rowspan="2">90</td>
<td>2.5</td>
<td>15.8</td>
<td>3035.8</td>
<td>5.5</td>
<td>107.8</td>
<td>0</td>
</tr>
<tr><td>2.5</td>
<td>14.3</td>
<td>3033.4</td>
<td>6.5</td>
<td>69.6</td>
<td>0</td>
</tr>
<tr><td rowspan="2">450</td>
<td rowspan="2">2</td>
<td rowspan="2">3808</td>
<td rowspan="2">100</td>
<td>2.6</td>
<td>99.5</td>
<td>9001.9</td>
<td>32.5</td>
<td>496.2</td>
<td>0</td>
</tr>
<tr><td>2.5</td>
<td>80.6</td>
<td>9002.7</td>
<td>31.5</td>
<td>464.9</td>
<td>0</td>
</tr>
<tr><td rowspan="2">500</td>
<td rowspan="2">3</td>
<td rowspan="2">4312</td>
<td rowspan="2">100</td>
<td>3.0</td>
<td>864.2</td>
<td>9319.8</td>
<td>73.5</td>
<td>1824.5</td>
<td>0</td>
</tr>
<tr><td>3.1</td>
<td>920.6</td>
<td>21002.3</td>
<td>73.5</td>
<td>1998.7</td>
<td>0</td>
</tr>
</tbody>
</table>
<p>1base、2pthread-unlimited よりも良好な結果です。2スレッドで1baseを実行している状態なので、単純に1baseの倍の性能を期待したくなりますが、今回の測定ではもっと早く限界に達したようです。</p>
<p>秒間350件ずつまでの負荷では良好ですが、秒間400件ずつになるとCPU使用率は90%に達し、3秒を越えるセッションが発生し始めます。
これが限界かと思いきや、秒間450件ずつに上げてもエラーは発生せず、大半のセッションはやはり数十msec以内で応答を返しています。</p>
<p>ただし、CPUは100%使い切っており、最長セッションは九秒にもなります。さらに負荷を上げてもこの傾向は変らず最長セッションの悪化幅が拡がります。2pthread-unlimited と比較すると、当然ながら、メモリ消費はずっと少なく済んでいます。</p>
<p>簡単にまとめると、少なくとも今回の測定では、性能面では2pthread-unlimitedに、安定性は1baseにそれぞれ匹敵し、それでいて上限はもっと高いと言えます。</p>
</div>
<div class="section" id="epoll">
<h3>4epoll</h3>
<p>表1.5 測定結果―4epoll</p>
<table border="1" class="docutils">
<colgroup>
<col width="7%" />
<col width="12%" />
<col width="10%" />
<col width="7%" />
<col width="7%" />
<col width="12%" />
<col width="13%" />
<col width="12%" />
<col width="12%" />
<col width="7%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head" rowspan="3">&nbsp;</th>
<th class="head" colspan="3">vmstat</th>
<th class="head" colspan="6">httperf</th>
</tr>
<tr><th class="head" rowspan="2">procs
(r+b)</th>
<th class="head" rowspan="2">mem
(KB)</th>
<th class="head" rowspan="2">CPU
(%)</th>
<th class="head" colspan="6">応答（msec）</th>
</tr>
<tr><th class="head">min</th>
<th class="head">avg</th>
<th class="head">max</th>
<th class="head">med</th>
<th class="head">stddev</th>
<th class="head">err</th>
</tr>
</thead>
<tbody valign="top">
<tr><td rowspan="2">350</td>
<td rowspan="2">5</td>
<td rowspan="2">3740</td>
<td rowspan="2">79</td>
<td>2.4</td>
<td>9.2</td>
<td>62.1</td>
<td>5.5</td>
<td>8.8</td>
<td>0</td>
</tr>
<tr><td>2.5</td>
<td>9.7</td>
<td>61.4</td>
<td>5.5</td>
<td>9.3</td>
<td>0</td>
</tr>
<tr><td rowspan="2">400</td>
<td rowspan="2">27</td>
<td rowspan="2">5508</td>
<td rowspan="2">91</td>
<td>2.5</td>
<td>19.3</td>
<td>3037.2</td>
<td>9.5</td>
<td>52.4</td>
<td>0</td>
</tr>
<tr><td>2.5</td>
<td>19.4</td>
<td>109.1</td>
<td>10.5</td>
<td>21.5</td>
<td>0</td>
</tr>
<tr><td rowspan="2">450</td>
<td rowspan="2">21</td>
<td rowspan="2">6500</td>
<td rowspan="2">100</td>
<td>2.5</td>
<td>83.5</td>
<td>8999.8</td>
<td>44.5</td>
<td>348.4</td>
<td>0</td>
</tr>
<tr><td>2.5</td>
<td>93.5</td>
<td>9008.9</td>
<td>44.5</td>
<td>453.5</td>
<td>0</td>
</tr>
<tr><td rowspan="2">500</td>
<td rowspan="2">28</td>
<td rowspan="2">7260</td>
<td rowspan="2">100</td>
<td>5.0</td>
<td>649.4</td>
<td>9118.1</td>
<td>98.5</td>
<td>1572.1</td>
<td>0</td>
</tr>
<tr><td>3.0</td>
<td>608.2</td>
<td>9137.7</td>
<td>97.5</td>
<td>1493.2</td>
<td>0</td>
</tr>
<tr><td rowspan="2">550</td>
<td rowspan="2">25</td>
<td rowspan="2">6988</td>
<td rowspan="2">100</td>
<td>3.0</td>
<td>1225.9</td>
<td>9123.3</td>
<td>105.5</td>
<td>2280.7</td>
<td>5</td>
</tr>
<tr><td>6.8</td>
<td>1312.3</td>
<td>21006.0</td>
<td>105.5</td>
<td>2565.8</td>
<td>0</td>
</tr>
</tbody>
</table>
<p>4epollはシングルスレッド構成ですが、I/Oを多重化しているため複数のセッションを同時に処理します。本来はCPU使用率が50%を越えることはない構成ですが、ダミー処理を追加しセッションごとに子プロセスを生成しているため、今回の測定では50%を簡単に越えています。</p>
<p>測定結果は3pthread-pollに良く似ています。秒間350件ずつまでは良好、秒間400件ずつになると遅いセッション (最長で3秒を越える) ものが出始めます。秒間450件ずつでは九秒に達します。良好な応答をしている場合も、応答時間の平均値は3pthread-pollよりも若干長くなっており、またスケジューリングの待ち行列も長めになっています。これは同期的に待ち合わせないための処理がオーバヘッドになっていると考えられます。</p>
<p>負荷を上げていくと応答性能などは当然悪化しますが、3pthread-pollと比較すると悪化幅はやや小さく、負荷に対してはより強い傾向がうかがえます。</p>
</div>
<div class="section" id="epoll-multi">
<h3>5epoll-multi</h3>
<p>5epoll-multiは動作環境として、もっと多くのCPU数を想定しているため、今回の測定/比較は理想的ではありませんが、現実にはそうも言っていられません。</p>
<p>構造的には 4epollを2つ（本来はCPU数 - 1）起動した形になりますが、 <tt class="docutils literal">accept(2)</tt> は別プロセスで処理するため、その分のオーバヘッドがあります。このオーバヘッドは負荷が秒間350件ずつの場合から見て取れます。</p>
<p>測定結果も4epollに良く似ていますが、耐負荷性能という点ではさらに上がっている傾向がうかがえます。オーバヘッドのため、最短セッション秒数は4epollよりも長く（悪く）なっていますが、最長セッションの方は短くなっています。秒間350件ずつの場合で4epollが9秒に達しているのに対し5epoll-multiでは350msec 程度となっており、バラツキも少なくなっています。</p>
<p>秒間500件ずつの場合を4epollと比較すると、4epollの最短セッション秒数が悪化し5epoll-multiと同程度になり、中央値も延び、バラツキが拡がるのに対し、5epoll-multiの最短セッション秒数は悪化せず、中央値の悪化幅は小さく、最長セッション秒数の優位は保っています。</p>
<p>簡単にまとめると負荷が低い場合にはオーバヘッドのため4epollよりも時間がかかるが、高負荷に対しては耐性が期待できると言えます。</p>
<p>表1.6 測定結果―5epoll-multi</p>
<table border="1" class="docutils">
<colgroup>
<col width="7%" />
<col width="12%" />
<col width="10%" />
<col width="7%" />
<col width="9%" />
<col width="12%" />
<col width="12%" />
<col width="12%" />
<col width="12%" />
<col width="7%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head" rowspan="3">&nbsp;</th>
<th class="head" colspan="3">vmstat</th>
<th class="head" colspan="6">httperf</th>
</tr>
<tr><th class="head" rowspan="2">procs
(r+b)</th>
<th class="head" rowspan="2">mem
(KB)</th>
<th class="head" rowspan="2">CPU
(%)</th>
<th class="head" colspan="6">応答（msec）</th>
</tr>
<tr><th class="head">min</th>
<th class="head">avg</th>
<th class="head">max</th>
<th class="head">med</th>
<th class="head">stddev</th>
<th class="head">err</th>
</tr>
</thead>
<tbody valign="top">
<tr><td rowspan="2">350</td>
<td rowspan="2">6</td>
<td rowspan="2">4572</td>
<td rowspan="2">82</td>
<td>2.5</td>
<td>11.0</td>
<td>103.7</td>
<td>5.5</td>
<td>13.7</td>
<td>0</td>
</tr>
<tr><td>2.5</td>
<td>11.6</td>
<td>80.4</td>
<td>5.5</td>
<td>14.1</td>
<td>0</td>
</tr>
<tr><td rowspan="2">400</td>
<td rowspan="2">21</td>
<td rowspan="2">6088</td>
<td rowspan="2">94</td>
<td>2.5</td>
<td>23.8</td>
<td>181.7</td>
<td>10.5</td>
<td>30.2</td>
<td>0</td>
</tr>
<tr><td>2.5</td>
<td>24.7</td>
<td>181.9</td>
<td>11.5</td>
<td>30.6</td>
<td>0</td>
</tr>
<tr><td rowspan="2">450</td>
<td rowspan="2">21</td>
<td rowspan="2">8592</td>
<td rowspan="2">100</td>
<td>2.5</td>
<td>120.2</td>
<td>349.8</td>
<td>110.5</td>
<td>76.1</td>
<td>0</td>
</tr>
<tr><td>2.5</td>
<td>121.5</td>
<td>348.1</td>
<td>110.5</td>
<td>76.4</td>
<td>0</td>
</tr>
<tr><td rowspan="2">500</td>
<td rowspan="2">19</td>
<td rowspan="2">15612</td>
<td rowspan="2">100</td>
<td>3.0</td>
<td>672.1</td>
<td>1160.4</td>
<td>746.5</td>
<td>306.7</td>
<td>0</td>
</tr>
<tr><td>3.2</td>
<td>675.5</td>
<td>1141.6</td>
<td>751.5</td>
<td>305.6</td>
<td>0</td>
</tr>
<tr><td rowspan="2">550</td>
<td rowspan="2">46</td>
<td rowspan="2">21160</td>
<td rowspan="2">100</td>
<td>3.9</td>
<td>1484.2</td>
<td>9123.7</td>
<td>810.5</td>
<td>1963.2</td>
<td>16</td>
</tr>
<tr><td>2.9</td>
<td>1485.1</td>
<td>9128.6</td>
<td>808.5</td>
<td>1970.4</td>
<td>17</td>
</tr>
<tr><td rowspan="2">600</td>
<td rowspan="2">45</td>
<td rowspan="2">18428</td>
<td rowspan="2">100</td>
<td>14.6</td>
<td>1608.2</td>
<td>9236.1</td>
<td>747.5</td>
<td>2271.9</td>
<td>526</td>
</tr>
<tr><td>2.9</td>
<td>1599.2</td>
<td>9238.8</td>
<td>743.5</td>
<td>2274.6</td>
<td>511</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="section" id="id25">
<h2>おわりに</h2>
<p>測定結果について補足しておきます。応答にバラツキがあり、数秒もかかるものがあるからと言っても、それだけでそのサーバがまるでダメとは言い難いものがあります。</p>
<p>これは考え方次第かもしれませんが、大半のものが設定した上限、例えば1秒以内に応答が返され、ごく少数のものだけがそれを越えるという状況ならば、許容できるかもしれません。言い方を替えると、設定した上限に100%収まることが条件か、それとも95%ならば合格とするのかということになると思います。100%を条件とすると、サーバ台数など過剰に設備投資することになり、通常状態では多くの設備が遊んでしまうという無駄を生みかねません。</p>
<p>前記事の内容を確認する意味もあり、今回改めて測定してみました。結果はおおむね予想通りですが、ダミー処理の追加など新たな技術的テーマも追加しています。マルチスレッドを否定するわけではありませんが、高性能を期待する場合は epoll によるイベントループの方が適切な場合も考えられ、読者のソフトウェア開発において、前記事、本記事が一助となれば幸いです。</p>
<p>（初出: H22/2）</p>
<p>Copyright © 2010 SENJU Jiro</p>
<p class="footnote">本稿で取り上げた内容は、記事の執筆時の情報に基づいており、その後の実装の進展、変更等により、現在の情報とは異なる場合があります。
本連載「プログラマーズ・ハイ」では読者からのご意見、ご感想を切望しています。今後取り上げるテーマなどについてもご意見をお寄せください。</p>
]]>
    </content>
</entry>

<entry>
    <title>epollインタフェースとsignalfd（1）</title>
    <link rel="alternate" type="text/html" href="http://www.oreilly.co.jp/community/blog/2010/05/epoll-interface-and-signalfd-part1.html" />
    <id>tag:www.oreilly.co.jp,2010:/community/blog//4.621</id>

    <published>2010-05-17T06:19:29Z</published>
    <updated>2011-06-30T00:11:33Z</updated>

    <summary> 「インターネットサーバでのPthreadとepoll」の記事（以下、前記事と呼びます）を書いた時点では、手元の環境がプアなためマルチプロセス/マルチスレッドを採用したサンプルプログラムの真価を発揮さ...</summary>
    <author>
        <name>千住治郎</name>
        
    </author>
    
        <category term="Programmer&apos;s High" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="ja" xml:base="http://www.oreilly.co.jp/community/blog/">
        <![CDATA[<img alt="http://www.oreilly.co.jp/community/blog/images/union/pg_high_logo.png" src="http://www.oreilly.co.jp/community/blog/images/union/pg_high_logo.png" />
<p class="lead">「インターネットサーバでのPthreadとepoll」の記事（以下、前記事と呼びます）を書いた時点では、手元の環境がプアなためマルチプロセス/マルチスレッドを採用したサンプルプログラムの真価を発揮させられず、適切に比較できませんでしたが、その後デュアルコアマシンを借りることができたので、改めて比較してみました。
また、比較の際にサンプルプログラムに追加したダミー処理ではシグナルも使用したので、やはりLinuxに追加された <tt class="docutils literal">signalfd(2)</tt> もepollによるイベントループで処理してみました。</p>
<p class="footnote docutils">
本記事のサンプルコードは、以下のリンクよりダウンロードすることができます。記事と合わせてご参照ください。<br />
[ <a class="reference external" href="http://www.oreilly.co.jp/pub/pg_high/ph20100223.tar.bz2">サンプルコード</a> ]</p>
<h2>前記事のサンプルプログラム</h2>
<p><a class="reference external" href="http://www.oreilly.co.jp/community/blog/2010/03/pthread-epoll-inet-server-part1.html">前記事</a> ではHTTPサーバを例に並列性/多重性のサンプル実装を5種類提示しました。簡単に振り返ります。サンプルプログラムがデュアルコアシステム上で動作しており、HTTPリクエストがほぼ同時に3件届いた場合のインタリーブ例を挙げてみます。毎回必ずこの通りにインタリーブされるわけではなく、あくまでもそれぞれの動作の違いを示すための一例です（ <a class="reference internal" href="#id4">図1.1</a> から <a class="reference internal" href="#id8">図1.5</a> ）。
また、5epoll-multiはCPU数-1個の4epoll（相当）を子プロセスとして起動しますが、デュアルコアシステムでは子プロセスの4epollを1つしか起動せず、4epollとの比較がしにくくなるため、今回は測定用にCPU数分の4epollの子プロセスを起動することにします。</p>]]>
        <![CDATA[<p><span class="target" id="id4">図1.1</span> インタリーブ例―1base</p>
<img alt="fig/epoll2-fig1.png" src="fig/epoll2-fig1.png" />
<ul class="simple">
<li>シングルスレッドで一セッションづつ処理する</li>
<li>1セッションを完了しないと次のセッションへ進まない、同時に処理しない</li>
<li>CPU2を使用しない</li>
</ul>
<p><span class="target" id="id5">図1.2</span> インタリーブ例―2pthread-unlimited</p>
<img alt="epoll2-fig2.png" src="http://www.oreilly.co.jp/community/blog/2010/05/fig/epoll2-fig2.png" />
<ul class="simple">
<li>スレッド数に上限を設けず、要求を受ける度に新規スレッドを作成し、1スレッドが1セッションを専門に処理する。上例では3スレッドを作成する</li>
<li>セッションA、Cを担当する2スレッドは同じCPU上で同時に実行される</li>
<li>セッションA、Bを担当する2スレッドはそれぞれ異なるCPU上で実行される</li>
</ul>
<p><span class="target" id="id6">図1.3</span> インタリーブ例―3pthread-pool</p>
<img alt="epoll2-fig3.png" src="http://www.oreilly.co.jp/community/blog/2010/05/fig/epoll2-fig3.png" />
<ul class="simple">
<li>CPU 数分のスレッドをあらかじめ作成しておき、各スレッドが要求を受け付け逐次処理する。上例では2スレッドを作成済み</li>
<li>セッション A、B は並列化されるが、セッションCは逐次的に処理される</li>
</ul>
<p><span class="target" id="id7">図1.4</span> インタリーブ例―4epoll</p>
<img alt="epoll2-fig4.png" src="http://www.oreilly.co.jp/community/blog/2010/05/fig/epoll2-fig4.png" />
<ul class="simple">
<li>シングルスレッドで epoll によるイベントループを用い I/O を多重化し、複数セッションを同時に処理する</li>
<li>1セッションの完了を待たずに次のセッションの処理を開始する</li>
<li>全セッションが同じ CPU 上で同時に処理される</li>
</ul>
<p><span class="target" id="id8">図1.5</span> インタリーブ例―5epoll-multi(子プロセス数増加後)</p>
<img alt="epoll2-fig5.png" src="http://www.oreilly.co.jp/community/blog/2010/05/fig/epoll2-fig5.png" />
<ul class="simple">
<li>上記4epoll.cをCPU数分の、スレッドではなく、子プロセスとして起動し、親プロセスで要求を受け付け、ソケットを子プロセスへ渡す。上例では2プロセスの4epollを起動済み</li>
<li>1セッションの完了を待たずに次のセッションの処理を開始する</li>
<li>セッションA、Cは同じCPU上で同時に処理される</li>
<li>2pthread-unlimited.cではシステムが三スレッドをスケジューリングした結果としてセッションA、CがCPU1、セッションBがCPU2上で処理されたが、5epoll-multi.cではそれぞれのCPU上で4epollが動作し、1プロセス内のI/Oの多重化により同時処理が実現されている点が異なる</li>
</ul>
<p>構造をまとめると次の表になります（ <a class="reference internal" href="#id9">表1.1</a> ）。</p>
<p><span class="target" id="id9">表1.1</span> 前記事サンプルプログラムの構造</p>
<table border="1" class="docutils">
<colgroup>
<col width="31%" />
<col width="21%" />
<col width="23%" />
<col width="24%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head">サンプルプログラム</th>
<th class="head">スレッド
or プロセス数</th>
<th class="head">要求受付役</th>
<th class="head">同時処理可能
セッション数</th>
</tr>
</thead>
<tbody valign="top">
<tr><td>1base.c</td>
<td>1</td>
<td>&nbsp;</td>
<td>1</td>
</tr>
<tr><td>2pthread-unlimited.c</td>
<td>無制限</td>
<td>マスタスレッド</td>
<td>無制限</td>
</tr>
<tr><td>3pthread-pool.c</td>
<td>CPU数</td>
<td>各スレッド</td>
<td>CPU数</td>
</tr>
<tr><td>4epoll.c</td>
<td>1</td>
<td>&nbsp;</td>
<td>OPEN_MAX</td>
</tr>
<tr><td>5epoll-multi.c</td>
<td>CPU数 + 1</td>
<td>親プロセス</td>
<td>CPU数×OPEN_MAX</td>
</tr>
</tbody>
</table>
<ul class="simple">
<li>OPEN_MAX = 1 プロセスがオープン可能なファイル数。</li>
<li>比較のため5epoll-multi.cには子プロセス数を増加してある。</li>
</ul>
</div>
<div class="section" id="id10">
<h2>人為的な処理追加</h2>
<p>HTTPサーバとして前記事のサンプルプログラムを動作させると、その1セッションあたりの内容は若干数のシステムコールだけと言ってもよいものなので、サーバ側でのCPU使用率は <tt class="docutils literal">user%</tt> 、 <tt class="docutils literal">sys%</tt> の内、 <tt class="docutils literal">sys%</tt> ばかりが増加します。さらにサーバ側が負荷らしいレベルに達する前にHTTPクライアント役のCeleronの方が一杯になってしまい、充分な負荷を生成できません。</p>
<p>クライアント役には古いPentium 4も加え2台としましたが、サンプルプログラムにもう少し仕事をさせないと、まともな比較ができません。何もせずに時間だけを稼ぐ <tt class="docutils literal">sleep(3)</tt> を加えようか、いやそれではやはりCPU負荷にはならないし、スケジューリングにもおかしな影響を与えてしまう。ではCPU負荷を与えるために単純な計算、例えば32ビット整数がオーバフローするまで1ずつ足し算をしようか。いやそれではCPUを必要以上に手放さなくなるので、やはりおかしなダミー処理だろう、などと考えた結果、子プロセスを作成し、 <tt class="docutils literal">/tmp</tt> を <tt class="docutils literal">find(1)</tt> することにしました。</p>
<p>ちょうど、手元の環境の <tt class="docutils literal">/tmp</tt> を <tt class="docutils literal">df <span class="pre">-i</span></tt> した時に15個のinodeを使っていることが目に止まり、これを <tt class="docutils literal">find</tt> するとごく短時間で終了するシステムコールばかりを発行することになり、HTTPサーバが動的コンテンツとして純粋な外部モジュールを起動し、ディレクトリリストを得る動作っぽいかなという程度の着想です(そんなモジュールが現実に存在するかは知りませんが)。</p>
<p>1セッションごとに毎回fork/execするのはあまり現実的とは言えませんし、また一般的に考えてもこういう人為的な操作はよろしくないとは思いますが、目的は比較測定と割り切り、この条件を測定環境/方法として固定することにします（ <a class="reference internal" href="#id11">図1.6</a> ）。</p>
<p><span class="target" id="id11">図1.6</span> ダミー処理</p>
<img alt="epoll2-fig6.png" src="http://www.oreilly.co.jp/community/blog/2010/05/fig/epoll2-fig6.png" />
<p><tt class="docutils literal">fork(2)</tt> 後の部分（図中では"..."）は別のCPU上で実行されることもありますが、HTTPリクエスト読み取りとファイル送信の順序が変るわけではなく、サンプルプログラムの基本的構造に変化はありません。</p>
<p class="footnote">子プロセス生成により実際にはすべてのサンプルプログラムがマルチプロセス化し、スケジューリングに影響を与えますが、ここでは追及しないことにします。</p>
<div class="section" id="base">
<h3>1base</h3>
<p>1baseへのダミー処理の追加は比較的容易です。また、先のインタリーブ例も基本的には変化しません。セッションを同期的に処理するため、ダミーの子プロセスの終了も単にその場で待ち合わせます。先のインタリーブ例（ <a class="reference internal" href="#id4">図1.1</a> ）と比較のため、 <a class="reference internal" href="#id12">図1.7</a> を示します。</p>
<p>もちろんダミーの子プロセスが別のCPU上で実行されることもあり得ますが、ここではシングルスレッドで同期的に待ち合わせる点に注目してください。</p>
<p><span class="target" id="id12">図1.7</span> ダミー処理の追加―1base</p>
<img alt="epoll2-fig7.png" src="http://www.oreilly.co.jp/community/blog/2010/05/fig/epoll2-fig7.png" />
</div>
<div class="section" id="pthread-unlimited">
<h3>2pthread-unlimited</h3>
<p>同様にダミー処理を容易に追加できます。ソースコード上の変更は 1baseと変らず、やはり同期的に子プロセスの終了を待ち合わせるにも関わらず、マルチスレッド構造が効果を発揮し、複数のセッションを同時に処理できます。インタリーブを考えると子プロセスの存在が影響を与えますが、前述のようにここでは追及せず、2pthread-unlimitedのソースコード上は同期的に待ち合わせるようになっていても、マルチスレッドのおかげで複数のセッションを同時に処理できる点に注目してください。同様に比較インタリーブ例を示します（ <a class="reference internal" href="#id13">図1.8</a> ）。</p>
<p><span class="target" id="id13">図1.8</span> ダミー処理の追加―2pthread-unlimited</p>
<img alt="epoll2-fig8.png" src="http://www.oreilly.co.jp/community/blog/2010/05/fig/epoll2-fig8.png" />
<p>（CPU2 上のセッション B は割愛）</p>
</div>
<div class="section" id="pthread-pool">
<h3>3pthread-pool</h3>
<p>ソースコード上の変更は1baseと変らず、ダミー処理をやはり容易に追加できます。マルチスレッド構成ですが、CPU数ぶんしかスレッドを作成しないため、それ以上の数のセッションを同時には処理できません。そのため先のインタリーブ例から変化せず、 <a class="reference internal" href="#id6">図1.3</a> と <a class="reference internal" href="#id12">図1.7</a> が合体した形になります。CPU数以上のセッション、すなわち上例のセッションCはセッションAまたはBが終了しないとやはり開始されません。</p>
</div>
<div class="section" id="id14">
<h3>ソースコードの変更 (1)</h3>
<p>1base.c、2pthread-unlimited.c、3pthread-pool.cのソースコード変更は本質的に変らないため、ここでは1base.cのみを説明します。まず、ダミー処理の子プロセスを作成し、 <tt class="docutils literal">waitpid(2)</tt> により同期的に待ち合わせる関数 <tt class="docutils literal">dummy_busy()</tt> を追加します。実際にはepollによるイベントループにも対応する処理がありますが、これについては後述します。もっとも単純な <tt class="docutils literal">wait(2)</tt> に代わり、 <tt class="docutils literal">waitpid(2)</tt> を用いるのもイベントループ対応の一環です。</p>
<p>dummy_busy() 要約 (1)</p>
<pre class="literal-block">
dummy_busy(sock, do_wait)
{
    pid = fork();
    if (pid &gt; 0){
        /* 親プロセス */
        /* epollの場合 ... 後述 */

        if (do_wait)
            /* 子プロセスの終了待ち合わせ */
            waitpid(pid, &amp;status, 0);
    } else {
        /* 子プロセス―ダミー処理 */
        exec &quot;find /tmp &gt; /dev/null 2&gt;&amp;1&quot;;
    }
}
</pre>
<p>1base.cではHTTPリクエスト読み取り後にdummy_busy()をコールするだけです。詳細は付属サンプルコードを参照してください。</p>
<p>1base.cへのダミー処理追加要約</p>
<pre class="literal-block">
main()
{
    /*リスニングポート作成... */

    while(1) {
        accept();                 /* 接続受付 */
        read();                   /* HTTPリクエスト読み取り */
+       dummy_busy(/* wait */1);  /* ダミー処理 */
        write();                  /* HTTP レスポンスヘッダ送信 */
        sendfile();              /* 静的ファイル送信 */
        close();                 /* 接続切断*/
    }
}
</pre>
<p class="lead">&gt;&gt; <a href="http://www.oreilly.co.jp/community/blog/2010/06/epoll-interface-and-signalfd-part2.html">(2)につづく</a></p>
<p>（初出: H22/2）</p>
<p>Copyright © 2010 SENJU Jiro</p>
<p class="footnote">本稿で取り上げた内容は、記事の執筆時の情報に基づいており、その後の実装の進展、変更等により、現在の情報とは異なる場合があります。
本連載「プログラマーズ・ハイ」では読者からのご意見、ご感想を切望しています。今後取り上げるテーマなどについてもご意見をお寄せください。</p>
</div>]]>
    </content>
</entry>

</feed>

