admin のすべての投稿

str_replace, preg_replaceのパフォーマンス検証と呼び出しの最適化

概要

PHPでは文字列を置換するのにstr_replaceとpreg_replaceという関数を用いることができます、今回はそれぞれのパフォーマンスについて考察していきたいと思います。

予想としては当然preg_replaceのほうがコストが高く付くと思います、実際本当にそうなのか。またどの程度のパフォーマンスの開きが出るのかをいくつかのサンプルを用意して比較します。
簡単な表現から、多少複雑な表現まで検証してみます。
またそれぞれの関数の使用時の最適化についても調べたいと思います。

ちなみに当然ですがPHPの 公式マニュアル では特別な必要性がない限りstr_replaceを使用することを推奨しています。

preg_replaceとstr_replaceを理解する

preg_replaceとstr_replaceについてはご存じの方も多いかと思いますが、その名のごとくある文字列を対象として、特定の単語を別の単語で置き換えることのできる関数になります。
その違いも簡単で、str_replaceは特定の単語を検索置換するのに対して、preg_replaceは正規表現を用いて検索置換が行えます。

str_replaceについて簡単な例を示します。

$before = "this is test.";
$after = str_replace("this", "that", $before);
var_dump($after); // "that is test."

シンプルですね。文字列の置換ができます。

つづいてpreg_replaceについても簡単な例を示します。

$before = "this costs 1000yen. it costs 2000yen.";
$after = preg_replace("/(\d+)yen/", "¥$1", $before);
var_dump($after); // "this costs ¥1000. it costs ¥2000."

こちらも単純に正規表現を用いての置換ができることがわかりました。シンプルです。

性能を検証する

さて気になるパフォーマンスを見て行きましょう。
今回はテストケースを3つ用意しました。
1. 単純なパターンの単数の置換
2. 単純なパターンの複数の置換
3. 複雑なパターンの単数の置換

検証したPHPのバージョンは下記になります

$ php -v
PHP 5.4.30 (cli) (built: Jul 29 2014 23:43:29)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2014 Zend Technologies

単純なパターンの単数の置換を実行した際のパフォーマンス

ここでは単純なパターンでの一つの文字列の置換を実行します。
用意する計測するコードは下記のようになります。

<?php
if (!isset($argv[1])) {
  echo 'usage -- php profile.php {try_count}' . PHP_EOL;
}
$count = intval($argv[1]);
echo "run test {$count} times." . PHP_EOL;

$subject = "this is test sentence one. this is test sentence two. this is test sentence three. this is test sentence four. this is test sentence five. this is test sentence six. this is test sentence seven. this is test sentence eight. this is test sentence nine. this is test sentence ten.";
echo "check performance of str_replace." . PHP_EOL;
$start = microtime(true);
for($i=0; $i<$count; $i++) {
  $subject = str_replace('this', 'that', $subject);
}
$end = microtime(true);
var_dump($end - $start);

echo "check performance of preg_replace" . PHP_EOL;
$start = microtime(true);
for($i=0; $i<$count; $i++) {
  $subject = preg_replace('/this/', 'that', $subject);
}
$end = microtime(true);
var_dump($end - $start);

計測します。

$ php profile_test_2.php 100000
run test 100000 times.
check performance of str_replace.
double(0.15461802482605)
check performance of preg_replace
double(0.35560202598572)

このような単純なパターンでも約二倍程度の性能差があることが見て取れました。
予想通りの結果となり、検索置換をアプリケーションで行うようであれば特別な理由なくpreg_replaceを使用するべきでないですね。

単純なパターンの複数の置換を実行した際のパフォーマンス

続いて複数文字列を置換する際のパフォーマンスについて検証します。
下記のようにサンプルプログラムを用意します。

<?php
if (!isset($argv[1])) {
  echo 'usage -- php profile.php {try_count}' . PHP_EOL;
}
$count = intval($argv[1]);
echo "run test {$count} times." . PHP_EOL;

$subject = "this is test sentence one. this is test sentence two. this is test sentence three. this is test sentence four. this is test sentence five. this is test sentence six. this is test sentence seven. this is test sentence eight. this is test sentence nine. this is test sentence ten.";
$source = array('one', 'two', 'three', 'four', 'five', 'six', 'seven', 'nine', 'ten');
$dest = array(1,2,3,4,5,6,7,8,9,10);
echo "check performance of str_replace." . PHP_EOL;
$start = microtime(true);
for($i=0; $i<$count; $i++) {
  $subject = str_replace($source, $dest, $subject);
}
$end = microtime(true);
var_dump($end - $start);


$source = array('/one/', '/two/', '/three/', '/four/', '/five/', '/six/', '/seven/', '/nine/', '/ten/');
$dest = array(1,2,3,4,5,6,7,8,9,10);
echo "check performance of preg_replace" . PHP_EOL;
$start = microtime(true);
for($i=0; $i<$count; $i++) {
  $subject = preg_replace($source, $dest, $subject);
}
$end = microtime(true);
var_dump($end - $start);

計測します。

$ php profile_test_1.php 100000
run test 100000 times.
check performance of str_replace.
double(0.53684210777283)
check performance of preg_replace
double(1.4594769477844)

ここでも2倍から3倍程度の性能差が確認できました。
置換対象が複数になることでその性能差が開いていることが確認できます。

ここでちょっと気になるのは、str_replace, preg_replaceのそれぞれの性能を先の検証パターンと比較した時に性能が線形に悪化しているわけではなさそうだということです。
これについては別件として最後に検証を行うこととします。

複雑なパターンの単数の置換を実行した際のパフォーマンス

最後に複雑な正規表現を用いた際のパフォーマンスの違いについて見てみます。
下記のプログラムのように検索する表現を無駄に複雑にしてみます。

<?php
if (!isset($argv[1])) {
  echo 'usage -- php profile.php {try_count}' . PHP_EOL;
}
$count = intval($argv[1]);
echo "run test {$count} times." . PHP_EOL;

$subject = "this is test sentence one. this is test sentence two. this is test sentence three. this is test sentence four. this is test sentence five. this is test sentence six. this is test sentence seven. this is test sentence eight. this is test sentence nine. this is test sentence ten.";
echo "check performance of str_replace." . PHP_EOL;
$start = microtime(true);
for($i=0; $i<$count; $i++) {
  $subject = str_replace('this', 'that', $subject);
}
$end = microtime(true);
var_dump($end - $start);

$subject = "this is test sentence one. this is test sentence two. this is test sentence three. this is test sentence four. this is test sentence five. this is test sentence six. this is test sentence seven. this is test sentence eight. this is test sentence nine. this is test sentence ten.";
echo "check performance of preg_replace" . PHP_EOL;
$start = microtime(true);
for($i=0; $i<$count; $i++) {
  $subject = preg_replace('/([a-z]+) (is) (test)/', '$1 $2 $3', $subject);
}
$end = microtime(true);
var_dump($end - $start);

計測します。

$ php profile_test_3.php 100000
run test 100000 times.
check performance of str_replace.
double(0.14943814277649)
check performance of preg_replace
double(2.3758080005646)

すさまじい差ですね。(こんなケースはないかと思いますが)
正規表現による検索が、検索パターンによってコストが増大していることがわかります。

(番外編)置換対象が複数ある場合の関数呼び出し時の最適化について

検証パターン2の考察時にも触れましたが、複数の検索置換を行うときにstr_replace, preg_replaceは同様のインタフェースを用意しており、検索パターンと置換パターンを配列で渡すことができます。
複数の置換を実行するときに、置換を一回一回実行する方法と、置換パターンを配列で渡し一度に実行する方法の実装の仕方によってどれだけ差が出るかということを検証します。

プログラムを用意します。
一回一回実行する方法では、置換を行い、置換後の文字列に対して再度置換を行い、、、を繰り返します。

<?php
if (!isset($argv[1])) {
  echo 'usage -- php profile.php {try_count}' . PHP_EOL;
}
$count = intval($argv[1]);
echo "run test {$count} times." . PHP_EOL;

$subject = "this is test sentence one. this is test sentence two. this is test sentence three. this is test sentence four. this is test sentence five. this is test sentence six. this is test sentence seven. this is test sentence eight. this is test sentence nine. this is test sentence ten.";
echo "check performance of multiple call." . PHP_EOL;
$start = microtime(true);
for($i=0; $i<$count; $i++) {
  $subject = str_replace('one', 1, $subject);
  $subject = str_replace('two', 2, $subject);
  $subject = str_replace('three', 3, $subject);
  $subject = str_replace('four', 4, $subject);
  $subject = str_replace('five', 5, $subject);
  $subject = str_replace('six', 6, $subject);
  $subject = str_replace('seven', 7, $subject);
  $subject = str_replace('eight', 8, $subject);
  $subject = str_replace('nine', 9, $subject);
  $subject = str_replace('ten', 10, $subject);
}
$end = microtime(true);
var_dump($end - $start);

$subject = "this is test sentence one. this is test sentence two. this is test sentence three. this is test sentence four. this is test sentence five. this is test sentence six. this is test sentence seven. this is test sentence eight. this is test sentence nine. this is test sentence ten.";
echo "check performance of single call." . PHP_EOL;
$start = microtime(true);
$source = array('one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten');
$dest = array(1,2,3,4,5,6,7,8,9,10);
for($i=0; $i<$count; $i++) {
  $subject = str_replace($source, $dest, $subject);
}
$end = microtime(true);
var_dump($end - $start);

計測してみます。

 $ php profile_test_4.php 100000
run test 100000 times.
check performance of multiple call.
double(1.2376821041107)
check performance of single call.
double(0.51983189582825)

!!!
をを。思った以上に差が出てきた
約倍程度のパフォーマンスの差がでています。

と余談なんですが、他のPCでも同じプログラムの実行を行ったところ、上記の結果ほどの差は出なかった。
その際のバージョンは5.4.30であった。

$ php profile_test_4.php 100000
run test 100000 times.
check performance of multiple call.
float(0.44886112213135)
check performance of single call.
float(0.36096215248108)

なんだかしこり残るのでphpのバージョンを5.6にあげて検証してみます。

$ php -v
PHP 5.6.6 (cli) (built: Feb 20 2015 22:35:31)
Copyright (c) 1997-2015 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2015 Zend Technologies
    with Zend OPcache v7.0.4-dev, Copyright (c) 1999-2015, by Zend Technologies
    with Xdebug v2.2.5, Copyright (c) 2002-2014, by Derick Rethans
...
$ php profile_test_4.php 100000
run test 100000 times.
check performance of multiple call.
double(1.22190117836)
check performance of single call.
double(0.46425199508667)

悪化した!?!?!?

ここから考察できるのはどうやらphp5.4.30以降str_replaceの性能は悪くなっているようである。

どちらにしても最適化された呼び出しとしては配列で一度に渡したほうが高速である。

以上、もろもろパフォーマンスについて検証しました、参考にしていただければと思います。


yumとかリポジトリとかとりまとめ

yumコマンドを用いてよく新しいモジュールをインストールすることって多いと思います。
このコマンド、あまり細部まで理解していなくても検索して出てきたコマンドをそのまま実行してだまってyをタイプしておけば大体の場合動くと思います。

ここで「よし動いた大丈夫」って思っている方は多分この記事を見る必要はないと思います。

実際内部でどういうことが起こっているのか、レポジトリってなに?(epel?remi?)なぜいっぱいあるの?
とか結構疑問はいっぱい出てきます。

今日はその辺のyumを取り巻く環境について調べたいと思います。

yumコマンド概要を理解する

yumとはYellowdog Updater Modifiedの略であり(それはどうでもいいか)パッケージを管理するメタパッケージ管理システムである。
FedoraやCentOSなどRPMベースのディストリビューションの多くでよく利用されている。
Wikipediaから抜粋

レポジトリとは動作の確認できているコンパイル済みのバイナリファイルを集めたストレージのことで、自分でわざわざソースコードの入手・コンパイルなどを行わなくても簡単に導入できます。
またソフトウェアが必要としているソフトウェアの依存性も自動的に検出して、必要な物も一緒にインストールしてくれます。

レポジトリを理解する

CentOSでよくみるレポジトリepelやらremiについて調べましょう。

Red Hat Enterprise Linux (RHEL)向けのパッケージであり、RHELから分岐したディストリビューション(CentOSもここに含まれます)と互換性のあるパッケージです。
目的はFedoraで提供されているパッケージをRHEL系のディストリビューションでも互換性を提供することにあるようです。
Fedoraで提供されている高品質なパッケージをそのまま互換性をのと同時に、プロジェクトに関してもFedoraプロジェクトと同じガイドライン、ルール、ポリシーに従うことを順守しているようです。

またremiについても同様に、有志によって管理されているプロジェクトになります。
公式ページを参照するとremiの目的ですが、最新バージョンのPHPモジュールをFedoraやRHEL系のディストリビューションに提供することとあります。それぞれ目的が異なるんですね。

yumコマンドを理解する

yumでのよく使うコマンドの一例を紹介します。

コマンド 動作
install パッケージをインストールする
update パッケージを更新する
check-update アップデート可能なパッケージ一覧を表示する
remove パッケージをアンインストールする
list インストール可能なパッケージ一覧を表示する
search 指定したキーワードでパッケージを検索する
info パッケージの情報を表示する

リポジトリの追加の仕方を理解する

remiリポジトリを例に順をおって説明していきます。

  • rpmファイルをダウンロードする
  • ネットで検索したりしてremiレポジトリのrpmファイルをダウンロードします。rpmファイルのパスは頻繁に更新されるようなので注意が必要です。

    wget http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
    

    ここでrpmファイルとは。これまた(RPM Package Manager)とよばれ、Redhatにより開発されたこれまたパッケージ管理システムになります。
    yumは実際にはrpmのラッパーとして動作しており、実際にパッケージのインストールに関してはrpmにより行われます。
    rpmはパッケージをcpio形式で圧縮し、その中にspecファイル・バイナリまたはソースコードが含まれます。またパッケージを管理するためにBerkeley DBを採用していて、これによりローカルのパッケージ情報の管理がなされる。
    specファイルにはパッケージの名前や、概要、依存するパッケージ、バイナリパッケージのインストールパス、インストール前後に実行するスクリプトなどが書かれていて、これによってユーザが意識しなくてもインストールを円滑に行うことができます。

    rpmでは依存するパッケージは提供されますが、その実行はユーザに任せています。この辺りを自動的に解決するのがyumということになります。

  • rpmファイルをインストールする
  • rpm --upgrade --verbose --hash remi-release-6.rpm
    

    インストールされたかどうかを確認するためには下記のコマンドを利用します。

    # rpm -qa | grep remi-release-6
    remi-release-6.5-1.el6.remi.noarch
    

    実際にリポジトリを使用するにはenablerepoと明示的にオプションに指定することや、設定ファイルに優先順位などを記述することで可能になります。
    ここでは試しにremiリポジトリが提供しているphpに関するパッケージをかくにしてみましょう。

    # yum --enablerepo=remi list | grep php
    php.x86_64                                 5.4.32-1.el6.remi            @remi
    php-cli.x86_64                             5.4.32-1.el6.remi            @remi
    php-common.x86_64                          5.4.32-1.el6.remi            @remi
    php-mysql.x86_64                           5.4.32-1.el6.remi            @remi
    php-pdo.x86_64                             5.4.32-1.el6.remi            @remi
    ...
    

    結局内部で何をしているかを理解する(つもりだったがまたの機会に)

    大雑把なことわかったんですが、中身が気になりますので追っていきます。
    確認環境はCentOS6.6になります。

    vi /usr/bin/yum
    --
    #!/usr/bin/python
    import sys
    try:
        import yum
    except ImportError:
        print >> sys.stderr, """\
    There was a problem importing one of the Python modules
    required to run yum. The error leading to this problem was:
    
       %s
    
    Please install a package which provides this module, or
    verify that the module is installed correctly.
    
    It's possible that the above module doesn't match the
    current version of Python, which is:
    %s
    
    If you cannot solve this problem yourself, please go to
    the yum faq at:
      http://yum.baseurl.org/wiki/Faq
    
    """ % (sys.exc_value, sys.version)
        sys.exit(1)
    
    sys.path.insert(0, '/usr/share/yum-cli')
    try:
        import yummain
        yummain.user_main(sys.argv[1:], exit_code=True)
    except KeyboardInterrupt, e:
        print >> sys.stderr, "\n\nExiting on user cancel."
        sys.exit(1)
    

    python。
    ちなみにですがpythonは結構コマンドラインと親和性が高いように思っています。
    デプロイツールの一種にもpythonを拡張したfabricというライブラリがあり、コマンドラインなんかを用意に実行できます。

    そしてyumスクリプトを見ると実態はpythonのラッパーとして動作していることがわかります。

    この続きについてはまた機会があるときに追跡しようと思います。

    本日はyumの周りをざっくり理解してもらえたでしょうか。


    vim tips その4

    前回にひき続いてvimのtipsを紹介していきましょう。
    今回はより快適に作業するためにvimが編集作業をどのように扱うのかをバッファ・ファイルについてと、より効率的に作業するためにウインドウやタブの概念について説明します。

    バッファを理解する

    vimがメモリ上に保持して展開している内容のことをバッファと呼びます。
    vimで何かしらのファイルをオープンした時、ファイルの内容をバッファに読み込みます。
    編集を行っているときは実際にはバッファの内容をオンメモリで編集しており、リアルタイムにファイルに書きだされているわけではないのです。したがって当然バッファの編集を行った場合、バッファの内容とファイルの内容には差がでます。
    当然最終的には編集したバッファの内容をファイルに書き出すことで、作業した内容の永続化処理をおなうことになります。

    つまりウインドウから我々ユーザが見ている内容はvimのバッファの内容ということになります。
    感覚的にはウインドウはバッファを除くビューポートのような働きをしていることになります。

    試しにvimから複数のファイルパスを引数に与えて起動してみましょう。

    vim a.txt b.txt
    

    するとvimは内部でそれぞれのファイルに対応するバッファを展開します。
    この様子は下記のexコマンドを実行することで、展開しているバッファの様子を確認することができます。
    (exコマンドには前回説明しましたので、わからない方は参照してみてください。)

    :ls
    

    exコマンドを実行すると下記のように表示されます。

    :ls
      1 %a   "a.txt"                        line 1
      2      "b.txt"                        line 0
    Press ENTER or type command to continue
    

    1,2と各バッファに項番がふられている様子と、それぞれのバッファに対応しているファイルパスが確認できます。
    またaactiveを表し、現在参照しているバッファを指しています。

    このようにvimではバッファを複数持つことができるので毎回ファイルを開き直さなくても編集可能なようになっています。
    続いて各種バッファに対する操作についても見ていきます。

    バッファを移動する

    各バッファについてはexコマンドを用いて移動することができます。それぞれについて説明します。

    exコマンド 意味
    :bnext 次のバッファに移動する
    :bprev 前のバッファに移動する
    :bfirst 最初のバッファに移動する
    :blast 最後のバッファに移動する
    :buffer{N} N番目のバッファに移動する
    :buffer {bufname} 指定の名称のバッファに移動する

    バッファを操作する

    展開しているバッファすべてに対してexコマンドを適用するようなこともでき、:bufdoで実現することができます。
    一気にファイルを操作することができて非常に便利です。

    またバッファは当然削除することもできます。
    ここに関してはバッファ自体が削除されるだけで、実際に永続化されているファイルも削除されるわけではありません。

    exコマンド 意味
    :bufdo 展開しているバッファ全てに対してexコマンドを実行する
    :bdelete {n} {m} {l} 指定した項番のバッファを削除する
    :{n},{m} bdelete 指定した項番の間にあるバッファを全て削除する

    引数リストを理解する

    バッファリストと少し似たような概念として引数リストがあり、これは名前が示すように、vimが展開している引数のリストになります。

    現在の引数リストを表示するには下記のように実行します。

    :args
    

    先ほどと同様にvimにオープンするファイルを引数にあげて起動した状態から:argsコマンドを叩くとどのようになるかを確認します。
    結果としては下記のように、引数として与えたファイルが列挙される形で表示されます。
    カギカッコでくくられているファイルは現在アクティブである引数を表しています。

    [a.txt] b.txt
    

    引数リストに関して覚えておくべきことは、それが常に同じ状態を持っていることではないこと、それと引数リストを設定することができることです。
    引数リストを設定するというのは実際には、exコマンドを用いて新たにバッファを追加するという事になります。

    引数リストの設定については下記のようになります

    :args {arglist}
    

    上記のような性質から当然、引数リストの内容は常に変更可能であり、:argsの出力結果がバッファリストの内容と一致するという保証はないです。
    次に引数リストを用いてバッファをオープンする方法を見ましょう。

    余談ですが、引数リストに関してはviの機能で、バッファリストに関してはvimで拡張された機能になります。
    なので似たような振る舞いを保つ機能が2つ存在しているかのようになっているのですが、引数リストを応用することで便利なことができます。

    引数リストを用いてバッファを操作する

    先に述べたように引数リストを指定することでバッファを新たに展開することができます。
    これの便利なところは指定する表現にglobを使用することができる点です。

    たとえば特定のフォルダ配下のすべてのファイルをバッファに展開したい場合は下記のようになります。

    :args **/*
    

    知っている方にはおなじみで直感的にわかりやすいと思います。
    また当然glob表現であるため、その表現を利用できます。

    例えばtxtファイルだけをバッファに展開したい場合は下記のようになります。

    :args **/*.txt
    

    また:argsコマンドはバッククォートを展開することができるので、例えばプロジェクトのファイルリストを保持しているファイルがある場合、下記のようなコマンドを用いてプロジェクトのファイルを引数リストに展開することも可能になります。

    :args <code>cat filelist</code>
    

    また先にすべてのバッファに対してexコマンドを適用するコマンド:bufdoを取り上げましたが、引数リストに関しても同様に引数リストに存在するもの全てに対してexコマンドを適用するコマンド:argdoが存在します。

    この2つに関して言えることはバッファリストに関しては、展開しているすべてのバッファを含んでしまうことになりますが、引数リストに関しては作業状況に応じて変更することが可能なものです。
    引数リストはテンポラリな作業場のようにして使うことができるのです。

    隠しバッファを理解する

    隠しバッファとは一言で言うと、編集中(ダーティー)なバッファのことです。
    あるファイルを展開して、それを編集した後に、保存処理などを行わないで終了しようとすると警告がでます。
    これを意図的に無視することができ、編集したものを一時的においておき、別のバッファに映るときに隠しバッファを利用することができます。

    例えば、あるバッファを編集し、それを隠しバッファとして次のバッファに映るときには下記のようにタイプします

    :bnext!
    

    !マークをつけることで隠しバッファとすることができます。

    これが何に利用できるのかということなんですが、展開している全バッファに対して一気に操作を適用するときに利用します。(というかこれしないと:bufdoなどが使えないです)

    vimには隠しバッファを利用するかしないかの設定をグローバルに保持しており、それは下記のコマンドで有効にすることができます。

    :set hidden
    

    :bufdoなどの複数バッファにコマンドを展開するコマンドの共同に関して隠しバッファを利用しない場合、下記のようなフローになります。

    1. 先頭のバッファの内容に変更を適用する
    2. ユーザに対してインタラクティブに保存を求める
    3. 次のバッファに移動する

    なのですが項目2.で:bufdoが止まってしまうため使用できないのです。

    よって隠しバッファを有効にすることでダーティな状態を許容することができ、項目2.がなくなり:bufdoが適用可能になります。

    ウインドウを理解する

    vimにおけるウインドウはバッファを参照するビューポートのような働きをします。
    もちろんウインドウを複数作成することができるのですが、そのウインドウが覗き見ているバッファの内容はなんでも良いです。
    これによって異なるウインドウから同じバッファを見ることなども可能になります。

    ウインドウの操作について見て行きましょう。

    ウインドウの分割

    コマンド 意味
    Ctrl-w s ウインドウを水平に分割する
    Ctrl-w v ウインドウを垂直に分割する
    :split {filename} 指定のファイルを新しいウインドウでオープンする

    またウインドウで新しいバッファを覗き見たい場合は:edit {filename}の用にタイプすることで可能です。

    続いてカーソルをウインドウの間で移動するには下記のようにします。

    フォーカスの移動

    コマンド 意味
    Ctrl-w w 次のウインドウに移動する
    Ctrl-w h 左のウインドウに移動する
    Ctrl-w j 下のウインドウに移動する
    Ctrl-w l 右のウインドウに移動する
    Ctrl-w k 上のウインドウに移動する

    ウインドウを閉じる

    ノーマルコマンド exコマンド 意味
    Ctrl-w c :clo[se] 現在アクティブなウインドウを閉じる
    Ctrl-w o :on[ly] 現在アクティブなウインドウ以外のウインドウを全て閉じる

    ウインドウのサイズを変更する

    コマンド 意味
    {N} Ctrl-w _ 高さをN行に指定する
    {N} Ctrl-w | 幅をN行に指定する
    Ctrl-w = すべての高さと幅を同じにする
    Ctrl-w _ アクティブなウインドウの幅を最大化する
    Ctrl-w | アクティブなウインドウの高さを最大化する

    またウインドウに関してもバッファや引数リスト同様に、全ウインドウに対してexコマンドを実行するコマンドが存在し:windoで実行することができますので、覚えておくと良いと思います。

    タブを理解する

    vimではタブもまた保持することができ、vimにおけるタブとはウインドウのコレクションになります。
    特に難しいことはないと思いますので、ここでは各種コマンドをご紹介するに留めたいと思います。

    タブを操作する

    コマンド 意味
    :tabedit {filename} タブを開く(同時に新しいウインドウ・バッファで開かれることになります)
    Ctrl-w T 新しいタブを開いてウインドウをそこに移動する
    {N}gt 番号指定でタブを移動する
    gt 次のタブへ移動する
    gT 前タブへ移動する
    :tabclose アクティブなタブをクローズする
    :tabonly アクティブなタブ以外のタブをすべてクローズする

    いかがでしたでしょうか。
    作業を効率化するために自分なりのタブやウインドウの設定を見つけていくことで快適な作業環境を構築できるんじゃないかなと思います。


    vim tips その3

    前回にひき続いてvimのtipsを紹介していきましょう。今回はコマンドラインモードについてまとめたいと思います。

    コマンドラインモードとは

    vimは :(コロン)をタイプすることでコマンドラインモードという特殊なモードに遷移することができあらかじめ用意された組み込みの動作を実行することができます。

    コマンドラインモードの幾つかはノーマルモードで同等のことが実行でき、状況に応じて両者を使い分けることでより快適な編集ができることになると思います。

    コマンドの構成を理解する

    例えば三行目を削除するといった動作を考えてみましょう。
    これは:3dというコマンドで実現できます。
    まずはこのコマンドを紐解いていきます。
    コマンドラインモードへ映るには:をタイプします。
    さらにその後にある3はレンジを表します。今は単行を示していますが、複数行やファイル全体をレンジ対象とすることもできます。
    最後にdに関してですが、これは指定したレンジに対する動作を表し、以前説明したオペレータと同様のものになります。

    まとめるとバッファに対する編集操作を行うコマンドは下記のように構成されています

    構成要素 意味
    プレフィクス : コマンドラインを使用することを表す
    レンジ 3 編集する対象範囲を表す
    オペレータ d レンジに対する動作を表す

    基本的なレンジ指定方法を理解する

    先程は単行のレンジ指定を例として取り上げましたが、レンジ指定にはいろいろな指定ができ、これが非常に汎用性が高く便利なものなので、是非理解していきましょう。

    下記に主要なレンジ指定を示したいと思います。

    意味
    1,5 指定の行を範囲とする。例では1行目から5行目までをレンジとする
    .,5 .を用いて現在カーソルのある行を表せる。例では現在行から5行目までをレンジとする
    5,$ $を用いてファイルの末尾を表せる。例では5行目からファイルの末尾までをレンジとする
    % バッファ全体をレンジとする

    例としてファイルの5行目から10行目だけを削除したい場合は下記のようにコマンドをタイプします

    # 5行目から10行目を削除し、レジスタに格納する
    :5,10d
    

    またよくあるsubstituteコマンドによりファイル全体を置換するコマンドに関しても%を用いてバッファ全体をレンジに指定にしていると理解することができます

    # ファイル置換コマンド。%はバッファ全体をレンジとすることを意味し、sは置換オペレータの省略形を意味する
    :%s/before/after/g
    

    パターンによるレンジ指定方法を理解する

    直接的な行指定だけでなくコマンドにはパターンを用いたレンジ指定も可能です。

    意味
    /<html>/,/<\/html> 指定の行を範囲とする。例では1行目から5行目までをレンジとする

    はじめは少し見慣れないかもしれないですが、実態としてはスラッシュ区切りで開始パターンと終了パターンを指定しているだけで、そこまで複雑なものではありません。

    使用例として、たとえばbodyタグの内部をタグも含めて削除するような場合下記のようなコマンドとなります

    :/<body>/,/<\/body>/d
    

    下記のようなテキストファイルに対して上記のコマンドを実行します

    <html>
      <body>
        textext
      </body>
    </html>
    

    結果は下記になります。bodyタグ含む内部が削除されていることがわかりますね。

    <html>
    </html>
    

    またパターンに関して、パターンが存在する行からのオフセットを指定することもできます。

    例として、htmlタグの内部のみを削除するような場合下記のようなコマンドとなります

    :/<html>/+1,/<\/html>/-1d
    

    同様に先ほどのテキストに対してコマンドを実行すると結果は下記のようになり、bodyタグを除いて内部が削除されていることがわかりますね。

    <html>
      <body>
      </body>
    </html>
    

    コピー・移動コマンドを理解する

    先に取り扱ったコマンドと同じような形で指定行のコピーや移動を行うことができます。
    その構文は下記のようになります。これも見て行きましょう。

    :{source}{command}{desctination}
    

    それぞれのコマンドは下記のようになっています。

    動作 コマンド 意味
    バッファの内容をコピーする copy または t 任意の行のコピーを行う
    バッファの内容を移動する move または m 任意の行の移動を行う

    例えば下記のようなバッファに対して3行目の内容を4行目に移動したい場合を考えましょう。

    <html>
      <body>
        <i>hogehoge</i>
        <div><p>fugafuga</p></div>
      </body>
    </html>
    

    その際には下記のようなコマンドで実現することができます。

    :3t4
    

    単行での例となりましたが、複数行でのレンジ指定ももちろん使用可能ですので非常に汎用性の高いコマンドとなります。

    normalコマンドを理解する

    こちらも非常に汎用性が高いもので、指定のレンジに対してノーマルモードで行いたいオペレータを適用することができます。
    もちろんこちらはノーマルモードやビジュアルモードと併用したりしても同様のことが実現できます。
    コマンドモードからの実行のほうが効率が良いこともありますので是非覚えておきましょう。

    構文については下記のようになります

    :{レンジ指定}normal {ノーマルコマンド}
    

    例えばバッファ全体に対してコメントアウト(//)を追記したい場合下記のようにコマンドします

    :%normal i//
    

    一つ一つみていきましょう。
    %レンジを指定しているので対象がバッファ全体となります。
    その後にnomarlとタイプすることでnormalコマンドを実行できます。
    半角スペースをひとつ開けて、続いてタイプした内容が実際のノーマルコマンドの内容となります。
    ここではiを入力して挿入モードに遷移し//をタイプしているため、全行のコメントアウトということが実装できるわけです。

    またお察しの良い方は気づいているかもしれませんが、normalモードを実行する際にはカーソルの位置は暗黙的に行の先頭に初期化されます。

    コマンドラインウインドウを理解する

    vimでは実行したコマンド履歴を保存しており、それらを参照したり編集したりすることができます。
    コマンドラインウインドウを開くにはq:とタイプします。
    (すこし:qと紛らわしいですので注意ですね。)

    コマンドを実行すると過去の実行履歴がサブウインドウによって開かれます。
    コマンドラインウインドウは通常のvimウインドウと同じように編集することができ、編集を保存し終了するときなども同様に:wqで行うことができます。
    またUpキーやDownキーにより、過去のコマンドを選択することができ、エンターキーをタイプすると現在のウインドウに対して過去実行したコマンドを再度実行することが可能です。

    シェルとのやりとりを理解する

    またvimはシェルとも非常に親和性が高く、vimを起動中にシェルを呼び出したり、その標準出力や標準入力に関してやりとりすることが可能です。

    まずvimの中からshellを実行するには下記のようなコマンドになります

    :!{任意のコマンド}
    

    例えばlsコマンドを叩く場合は下記のようになります

    :!ls
    

    コマンドを実行すると一度vimではなくシェルウインドウが開き、結果が表示されます。
    その状態からエンターキーをタイプすると再びvimの画面へと戻ってくるような流れになります。

    ワンライナーでの実行ではなくシェルとインタラクティブに対話したいときは

    :shell
    

    と実行することでシェルが起動します。これは通常のシェルと同様で、再びvimに戻る場合はexitを用いてシェルを終了する必要があります。vimの編集している状態を残しつつ一時的にシェルを起動したい時など非常に有用だと思います。

    またバッファの内容をシェルとやりとりすることができます。

    vimからシェルを呼び出しその実行結果(標準出力)を現在のバッファに挿入する場合はreadコマンドを用います。
    例えばlsの結果をバッファに挿入したい場合は下記のようになります

    :read !ls
    

    逆にバッファの内容をシェルの標準入力に挿入する場合はwriteコマンドを用います。
    例えばphpスクリプトを記述していて、それをvimの中から呼び出す場合なんかは下記のようになります。

    :write !php
    

    最後に範囲指定で!コマンドを行うとバッファの内容をシェルに渡したあと、結果で選択範囲のバッファを上書きすることもできます。

    例えば下記のようなテキストがあった場合
    対象をgmail.comのユーザのみに絞り込みたいとします。

    id,name,email
    1,Bob,ex1@gmail.com
    2,Jobeth,ex2@yahoo.com
    3,Steven,ex3@gmail.com
    4,Smith,ex4@yahoo.com
    

    この際に2行目から最終行までをレンジ選択して、grepコマンドを用いてフィルタリング処理を行ってみましょう。

    :2,$ !grep @gmail.com
    

    結果として下記のようにフィルタリング処理が実行できています。

    id,name,email
    1,Bob,ex1@gmail.com
    3,Steven,ex3@gmail.com
    

    いかがでしたでしょうか。
    非常に汎用性の高いコマンドが多く、便利なものばかりですね。
    またシェルとのやりとりも覚えることで、別窓で処理したり、一度vimを閉じてから・・・などという手間もなくなるので覚えておきたいですね!


    vim tips その2

    さて前回にひき続いてその2ですが、今回は主にビジュアルモードについてまとめたいと思います。

    ビジュアルモードとは

    範囲選択をし、それを対象にしてオペレータコマンドを実行することができます。
    ちなみにこれはノーマルモードで「オペレータ」と「モーション」を組み合わせることでコマンドを実行することの逆転に等しいです。
    ビジュアルモードでは先に「モーション」を指定して、それに対する「オペレータ」を実行するように解釈できます。

    ビジュアルモードへ遷移する

    まずビジュアルモードには三種類存在します。
    ひとつは「文字指向」のビジュアルモードで文字単位での選択の際に利用できます。
    またひとつは「行指向」のビジュアルモードで行単位での選択に利用できます。
    最後に「ブロック指向」のビジュアルモードがあり、これは矩形選択を可能にします。

    それぞれのモードへ遷移するショートカットは下記のようになります。

    キーバインド 操作
    v 文字指向のビジュアルモードへ遷移する
    V 行指向のビジュアルモードへ遷移する
    Ctrl-v ブロック指向のビジュアルモードへ遷移する
    gv 直前のビジュアル動作を繰り返す

    ビジュアルモード間を移動する

    一旦なにかしらのビジュアルモードへ遷移すると、開始地点が固定される形となります。
    ビジュアルモードに遷移した後に、カーソルを移動すると現在のカーソル位置を終点として現在繊維中のビジュアルモードに依存した範囲選択が適用されます。

    ようするにビジュアルモードでは開始地点と終了地点とビジュアルモードの3つにより選択範囲が決定されます。
    そのうちビジュルモードに関しては、開始地点と終了地点を変更することなく、モードだけの変更が可能です。
    そのキーバインドはノーマルモードから遷移する際と同様です。シンプルですね。

    また特定のビジュアルモードにある状態で、もう一度そのモードに遷移するキーバインドを実行すると今度はノーマルモードに遷移します。
    つまり、ノーマルモードとビジュアルモードを切り替えるトグルとして機能するということです。これもまたシンプル。

    またoキーをタイプすることで、開始地点と終了地点を入れ替えることができます。
    これによって開始地点を誤って設定してしまった時に一度ビジュアルモードを抜けてノーマルモードになって・・・などという手順を踏まずに開始地点を変更することができます。便利。

    使いドコロ

    実際どんなことに使えるのかなというところをいくつか例にとってみてみましょう。

  • マルチカーソル(削除)
  • sublimeなんかである今っぽい感じの機能が擬似的に使用できます。
    例えば下記のような共通プレフィクスを消去したいときなどを考えましょう。

    <a href="http://prefix.example.com/A"></a>
    <a href="http://prefix.example.com/B"></a>
    <a href="http://prefix.example.com/C"></a>
    

    Ctrl-vを用いた矩形選択を行い、横一列、縦三行の矩形を選択します。
    あとはx(削除)をタイプすれば3行同時に編集することが可能です。こんな使い方もできます。

  • マルチカーソル(挿入)
  • 同様に複数行に一気に編集を行いたい場合、それも矩形選択で可能です。
    例えば下記のようなhttpリンクにさらにサブホスト名を追加したい場合を考えましょう。

    <a href="http://prefix.example.com/A"></a>
    <a href="http://prefix.example.com/B"></a>
    <a href="http://prefix.example.com/C"></a>
    

    まずははじめの行のprefixのpの位置にカーソルをおきCtrl-vで矩形選択ビジュアルモードへ移動します。
    そこからjjをタイプし、横一列、縦三行の矩形を選択します。
    そこからIキーをタイプすることで、挿入モードへ遷移できるので、そこで追加したサブホスト名を入力すれば完了です。

    この時点でははじめの一行目しか編集されていないように見えるが最後にノーマルモードへと遷移すると矩形選択していた三行に同じ編集が適用されます。

    マルチカーソルなどは最近のエディタから付随していた機能かと思いましたが、随分昔からあるんですね。素晴らしいです。

  • 水平線見出し
  • 行選択とテキスト置換処理を行うことでテキストベースでの区切りなんかを生み出せます。
    例えば下記のようなデータが有り、ヘッダとボディ項目の間に区切りを入れたい時なんかを考えましょう。

    key value
    hoge - 1
    fuga - 2
    

    カーソルを左上に置いた状態でyypからのVで行選択、さらにr-を押下することで行すべてをハイフンに置換でき、即席の区切りができたりします。

    欠点

    ビジュアルモードは万能というわけではなくノーマルモードによる操作のほうが適した場合もあります。

    とりわけ繰り返し動作を行う際に、意図したものと異なる結果となることが多いです。
    ビジュアルモードは操作を範囲として選択するため、繰り返した際にもその「オペレータ」として機能した範囲が以前の動作に引きずられて固定されます。

    ピンポイントに行の中間での可変長なエリアでの編集処理というところは苦手であるので、そういった時はオペレータを用いたノーマルモードでの編集が有効です。

    まとめ

    欠点も少しありますが、vim全体としては無くてはならない機能です。
    特にマルチカーソルは使いこなせば非常に作業効率を上げることが出来る機能であるので是非理解してほしい機能です。
    是非使用してみてください。


    vim tips その1

    みなさんvim使いこなしていますでしょうか?
    私はメインのエディタとしてはコーディング用にもプレーンテキスト編集用にもsublimeエディタを使用しております。
    sublime自体、初めから非常に使用しやすく設計されているためあまりカスタマイズしなくてもそこそこ使えますし、そもそも複雑なコマンドなどを覚えなくてもGUIベースで使用できるので学習コストが非常に低いので取っ付き易い。
    マルチカーソル機能などもあり、一気に編集できるので非常に重宝しています。非常にいいエディタですね。

    ただ当たり前なんですがGUIベースなので少々マウスなどのポインティングデバイスに手を伸ばす必要があり、より高速にタイピングをするという意味でvimなどのテキストベースのエディタがかなりよいです。

    今回は実践で使えそうなトピックとしてvimの便利なtipsを紹介していきます。

    さっそくですが、基礎的な部分を今回は紹介します。

    ノーマルモード、挿入モード、置換モードを理解する

    vimは起動した時点でノーマルモードで起動し、実際にテキスト編集を行うときには挿入モードや、置換モードに変更してからテキストを入力する必要があります。
    便利なショートカットなど踏まえて紹介します。

    挿入モードへ変更する

    基本としてiキーをタイプすることで挿入モードに遷移することができます。
    その他にもvimはいくつかのキーで挿入モードに遷移することができ、これらを使いこなすことで入力スピードを格段に向上することができます。

    行の末尾・先頭のテキストを変更したい場合

    Aキーをタイプすることでカーソルを行の末尾に遷移しつつ、かつ挿入モードに遷移することができます。
    カーソルを一番最後まで移動してからiキーをタイプするよりもだいぶ高速に入力することが可能になります。

    またaキーをタイプすることでも現在のカーソルがある位置から一つ後ろにカーソルをずらしつつ挿入モードに遷移することができます。
    これを行の末尾にカーソルを移動できる$キーや、行の先頭にカーソルを移動できる0キーと組み合わせることで行の末尾や先頭のテキスト編集を高速に行えます。

    挿入モードからノーマルモードへ戻る

    基本としてEscキーをタイプすることで挿入モードからノーマルモードへ戻ることが可能です。
    ただ現在のキーボードはたいていEscキーは左上に存在していると思いますので、何気にタイプするのが辛い位置にあります。
    そのため早く入力を可能にするためにCtrl-[キーを使用することをおすすめします。これもEscキーと同様に挿入モードからノーマルモードへ戻ることができます。
    こちらのほうがホームポジションから手をずらすことなくタイプ可能なため、癖をつけておくと良いです。

    置換モードで変更する

    挿入モードとは別に、既存の文字を上書きしていくような入力モードが存在し、これを置換モードと呼びます。
    このモードを使用するにはRキーをタイプします。ノーマルモードに遷移するには、挿入モードからノーマルモードへ戻る手順と同じように、EscキーやCtrl-[をタイプすることで可能です。

    ノーマル挿入モードを利用する

    実際にテキストを編集する際にはノーマルモードと挿入モードを頻繁に往復する必要があり、このモードチェンジが煩わしく感じることも少なく無いと思います。
    こういう時に利用できるのがノーマル挿入モードとなります。
    このモードは挿入モードにあるときに、一時的にノーマルモードのコマンドを一つだけ受け付け、その後すぐにまた挿入モードに戻ります。
    挿入モードにあるときにCtrl-oキーをタイプすることで、一時的にノーマルモードでのコマンドを受け付けられるような状態になります。
    この時受け付けられるコマンドは一つだけで、入力するとまたすぐに挿入モードに戻りますが、非常に重宝するコマンドです。

    以上簡単ですが使えそうなtipsをかいつまんで紹介しました。
    とくに最後のノーマル挿入モードに関しては、非常に便利で学習するべきなのに、ちょっとvimを触ったことのあるレベルの人くらいでも知らない人も多いのではないかと思います。

    またvim tips継続的に紹介していきます。


    PHPでYAMLファイルを取り扱う

    概要

    PHPでYAMLファイルを取り扱う方法について2つの方法を取り上げて解説したいと思います。
    PHPでは純正のyamlライブラリが標準では無いため、別途用意する必要があります。

  • yaml拡張モジュール
  • yaml拡張モジュールとはlibyamlを用いた拡張モジュールになります。
    cで記述されているため高速に動作することが見込まれます。

  • Spyc
  • また完全にPHPのみを用いて記述されたyamlライブラリも存在し、そのうちの一つにSpycというも有名なのがあるのでこれも取り扱います。公式ページ
    特徴としては拡張モジュールはインストール作業が必要なのに対してSpycに関してはPHPのみで記述されているライブラリなので、複雑なインストール作業は不要です。
    1ファイルで構成されているのでリンク先からファイルをダウンロードし、Spyc.phpをrequireするだけで使用できる状態になります。非常にお手軽です。

    準備する

    それぞれの実装が使用できるように準備しましょう。

  • yaml拡張モジュール
  • 拡張モジュールはlibyamlおよびlibyaml-develを事前にインストールした状態でpeclコマンドを用いてインストールすることができます。
    rootになるまたはsudoコマンド経由で下記のようなコマンドを実行すれば完了です。

    [root@localhost ~]# yum install libyaml
    ...
    [root@localhost ~]# yum install libyaml-devel
    ...
    [root@localhost ~]# pecl install YAML
    ...
    
  • Spyc
  • 対してSpycは先にも述べましたがソースコードをダウンロードしてきて使用したい箇所でrequireするだけです。
    最近ではgithubでソースコードを管理されているようなので下記のようにcloneしてくれば十分だと思います。

    git clone https://github.com/mustangostang/spyc/
    

    検証してみる

    先にSpycで動かしてみましょう
    使用箇所でrequireします。するとグローバルでspyc_load_fileという関数が使用可能になります。
    これは引数としてファイルパスを与えることで、ファイルを読み込んでyaml形式でパースを行います。
    下記のような感じでOKです。

    <?php
    require_once (dirname(__FILE__) . '/Spyc.php');
    
    $obj = spyc_load_file('hoge.yaml');
    var_dump($obj);
    

    hoge.yamlは下記のように記述しておきましょう。

    - foo
    - bar
    

    すると出力結果は下記のようになります。

    array(2) {
      [0]=>
      string(3) "foo"
      [1]=>
      string(3) "bar"
    }
    

    動きましたね〜。

    対してyaml拡張モジュールではphp.iniにyaml.soライブラリをロードするように追記します。
    するとyaml_parse_fileという関数がグローバルで使用可能になります。
    こちらも同様に引数としてファイルパスを与えることで、ファイルを読み込んでパースを行います。

    $obj = yaml_parse_file('hoge.yaml');
    var_dump($obj);
    

    こちらも同様に、動きましたね。

    ただこの2つにはかなり挙動の違いがあるような箇所もあります。
    例えば入力ファイルの中身を下記のように編集したとしよう

    foo : bar
    bar
    

    この内容をパースすると

    Spycでは下記のようにパースされる。

    array(2) {
      'foo' =>
      string(3) "bar"
      [0] =>
      string(3) "bar"
    }
    

    たいしてyaml拡張モジュールではワーニングが表示され返り値としてはfalseがかえってきます。
    パースに失敗しているのです。

    PHP Warning:  yaml_parse_file(): scanning error encountered during parsing: could not find expected ':' (line 3, column 1), context while scanning a simple key (line 2, column 1) in /tmp/hoge.php on line 2
    bool(false)
    

    はたしてこれらは一体どちらが正しいのか。
    異なるフォーマットが与えられた時にちゃんとしてくれないのは困る。

    これに関していえるのはbarの扱いをどのように解釈しているのか、である。

    yaml1.1の仕様書を参考にしてみる。
    パラパラっと見た感じ、スカラ値のみで構成されるような場合は定義されていない。
    したがって結果として拡張モジュールでパースエラーが発生しているほうが安全であると言える。
    Spycではこれを意図的しているのかしていないのかは分からないが、純粋に配列の頭に付け加える(index:0)ことでパースを試みる。

    また別な例として下記の例もある。
    yamlではいくつかのメタ文字を定義していて
    例えば~はNULLを意味するしyはtrueを、nはfalseを意味する。などがあります。
    これらは型情報も一緒に解釈されるべきであります。

    実際に下記のようなyamlデータを用意して食わせてみましょう。

    null: ~
    true: true
    false: n
    string: '12345'
    

    Spycでの出力結果は下記のようになります。

    array(4) {
      'null' =>
      NULL
      'true' =>
      bool(true)
      'false' =>
      bool(false)
      'string' =>
      string(5) "12345"
    }
    

    yaml拡張モジュールでは下記のようになります。

    array(3) {
      [""]=>
      bool(false)
      [1]=>
      bool(true)
      ["string"]=>
      string(5) "12345"
    }
    

    驚いたことにyaml拡張モジュールでの出力結果は、はっきり言って構成を壊してしまっているといってもいいです。
    yaml界では一般的なのだろうか。

    対してSpycではメタ文字が使用されたとしてもキーとして使用されているうちは一意にstringとして解釈されるようです。
    拡張モジュールの方ではキーで使用される際もメタ文字として解釈され、結果わけのわからないことになっちゃってます。

    実際に用途として拡張モジュールのように解釈されてしまうと困ることのほうが困るような気がします。
    この辺りは逆にSpycのほうが直感に沿った解釈をしてくれているのかなと思います。

    好みのチョイスであるところもあるかもしれませんが、今回私はSpycの方をチョイスして実装の方を進めることにしました。
    今のところそこまで悪いところはなさそうです。
    (それに余談ですがインフラ都合もあって簡単にミドルモジュールをいじれるような状況でもないので)

    とりあえず、一点だけSpycで直して欲しいところがあったりして、spyc_load_fileファイルは読み込むyamlファイルパスを指定することでファイルを読み込めるのですが、引数に存在しないファイルを指定した際にもなんと普通に動いちゃいます

    たとえば下記のようなプログラムを実行すると

    <?php
    require_once (dirname(__FILE__) . '/Spyc.php');
    // aaaaは存在しないファイル
    $hoge = spyc_load_file('aaaa');
    var_dump($hoge);
    

    下記のように出力されます。

    array(1) {
      [0]=>
      string(4) "aaaa"
    }
    

    これまずくない??
    個人的には返り値がfalseになるとか例外投げるとかいろいろ設計はあると思うんですが。
    明らかに設計ミス?なにか意図があるのかと理解に苦しみます。

    と、、まあ今のところそんなこともあるのですが、また追加で問題などあれば追ってレポートしたいと思います。


    xhprofを利用する

    アプリケーションのパフォーマンスを向上させるためにはプロファイラと呼ばれるツールを利用するのが早いです。
    phpではxhprofというプロファイラがメジャーであり今回はその導入手順や使用感について紹介します。

    インストール

    PECLから入れます。そんなに難しくありません。

  • ソースをダウンロード
  • wget http://pecl.php.net/get/xhprof-0.9.2.tgz
    
  • make
  • tar zxvf xhprof-0.9.2.tar
    cd xhprof-0.9.2/extension/
    phpize
    make
    make install
    
  • php.iniを編集
  • [xhprof]
    extension=xhprof.so
    xhprof.output_dir=/var/log/xhprof
    
  • ログディレクトリを作成
  • mkdir /var/log/xhprof
    chmod -R 777 /var/log/xhprof
    
  • apache再起動
  • service httpd restart
    
  • xhprofのテンプレディレクトリをドキュメントルート配下に移動
  • cp -r /tmp/xhprof-0.9.2/xhprof_html {document_root}/xhprof_html
    cp -r /tmp/xhprof-0.9.2/xhprof_lib {document_root}/xhprof_lib
    
  • プロファイリングしたい箇所にコードを埋め込みます
  • {document_root}は適宜自分の環境に置き換えてください。

    xhprof_enable(); // プロファイリング開始
    
    //
    // ** プロファイリングしたい処理をここに記述 **
    //
    
    $xhprof_data = xhprof_disable();    //stop profiler
     
    //  プロファイリングページへのリンクを追加
    include_once "{document_root}/xhprof_lib.php";
    include_once "{document_root}/xhprof_runs.php";
    $xhprof_runs = new XHProfRuns_Default();
    $run_id = $xhprof_runs->save_run($xhprof_data, $XHPROF_SOURCE_NAME);
    echo "<a href=\"http://{document_root}/xhprof_html/index.php?run=$run_id&source=$XHPROF_SOURCE_NAME\">xhprof Result</a>\n";
    

    結果

    実行すると結果ページヘのリンクが表示されます。クリックすることで結果の詳細を確認することができます。

    おぉー参照できましたね。なかなかにいいかんじです。

  • 各項目
  • 項目 意味
    Function Name 関数名
    Calls プロファイリングした期間でコールされた回数
    Calls% コール回数のパーセント表示
    Incl. Wall Time 処理にかかった時間のうち、その関数が消費した時間。なお処理をネストした場合全経過時間を含むことになるので、あまり参考することはないと思われる
    IWall% Incl. Wall Timeのパーセント表示
    Excl. Wall Time 実際にその関数のみが消費した時間。多くの場合はこの項目なんかを参照しながらプロファイリングすることになると思われる
    IWall% Execl. Wall Timeのパーセント表示

    なるほどかなり正確な情報が把握できて、最終的にボトルネックになっている箇所が確認できます。
    実際に動かしてみると、当然ですが大きく時間を消費していたのがネットワーク処理、ついでファイル処理などかなり実用的なレベルで参考することができそうです。

    余談

    ちなみに結構長い間フリーランスしてる同僚と、プロファイリングからの性能検証について雑談してて。
    現実的な現場感としては、こういったプロファイリングを常に実行してボトルネックをチェックすることが多いのかな。ということについて話してると、現実的には実際に何か問題が発覚してから調査する。ってのが多いようです。

    あとは細かな性能検証も大事なんですが、昔と比べてマシンのスペックが格段に向上しているのでアプリのチューニングを突き詰めていくよりかは、マシンを追加してインフラ面からパワーを上げて対応する事が多いです。
    (もちろん普通にボトルネックになっているような場合は除く。アプリのチューニングが不要と言っているわけではないです。)

    実際その通りで、僕もどちらかと言うと小難しいコンパクトなコードを書くよりかは、コストが多少高くつくとしても可読性の高いコードを書くように心がけています。
    (よくある普通のレベルの企業では)あまり出番の多いわけではさそうですが、覚えておくと絶対役に立ちますね。
    個人的には常にこのへんの意識は頭のなかにあるエンジニアでいたいです。


    Macでのe-Taxの利用はやめたほうが無難

    みなさん確定申告の準備はいかがでしょうか。そろそろ今年も始まりますね。
    かく言う私も今年から確定申告をしなければならい立場になりましたのでいろいろ準備進めています。

    いろいろ調べてみるとe-Taxというネット上から確定申告をすべて済ませることができる良さ気なサービスが有ったので使ってみることにしました。
    そこで掲題にもありますが、Macでいろいろハマりました。
    ブラウザによって動いたり動かなかったり。依存を吸収できていないっぽいようです。

    chromeはまあ、公式にサポートしなかったのでできないのはなっとくなのですが、サポート対象であるsafariでもダメでした。
    特に初めてやるんだったら素直にウインドウズ使っておくほうがトラブル少なくて済みそうです。
    同じような人もいると思うのでいろいろ書いておこうかと思います。

  • サポート対象とか

    公式サイト見てみると、国税庁の味気のないページとはちがって26年度の確定申告特集ページは今どきっぽい感じに仕上がってます。

    そこからetaxの手続きに進むことができます。
    すると初めに環境を確認するようなページが出ます。

    私の環境は Mac OS 10.9のsafari 7.0.6とあるな。うん大丈夫そうだと思いましたこの時は。

    safariの場合

    個人情報などを入力してページを進んでいきます。
    最終的に確定ボタンを押すことになり、登録したメールアドレスに仮確定のメールが飛んでくる。。。はずなんですが。
    ・・・画面真っ白。何度やっても最後のページで画面真っ白になります。

    こちらの入力した情報が変なのかなと思って、条件など変えて3回くらいやり直した。が結局わからずでした。

    chromeの場合

    ダメ元でchromeでやってみた場合、safariでひっかかっていた場所は正常に通過出来ました。
    ですがその後、電子証明書のパスワードを入れるところで、パスワード入力フォームが表示される前に「パスワード入力が確認できました」というような表示がされてダメです。

    この辺javaのアプレットっぽい機能を使用しているらしく、そもそもjavaのインストールをする必要もあり、さらにデフォルトのセキュリティレベルでは禁止されている操作なのでセキュリティレベルを下げる必要がある。
    そもそもセキュリティレベル下げること自体ちょっと抵抗が。公的な機関がこんなでよいのかと。。
    私は仕事柄そういうの慣れているからいいですが、これ素人がわかるのか?と思ったり。
    (下準備でルート証明書を手作業で追加してくださいとか、正直おしっこちびりました。こわい)

    これだから税務署では確定申告サポートを手厚くやっているんでしょうね。
    これはお年を召した方には絶対に無理だなーと思います。
    結局私はvmでウインドウズも入れていたのでそちらで登録し、無事に電子証明書の登録まで完了出来ました。
    素直にウインドウズで申請するほうがトラブルが少なくて良さそうです。

    余談ですが、うまく行かずに何度も登録を試行錯誤していたせいで後日、国税局?税務署?から電話がかかってきました。何事かと思いました。怖かった。


  • stat系コマンドさわり

    概要

    よくわからないけどPCが重い。アプリケーションが応答しなくなったなど今PC内部で何が起こっているのかざっくり把握したい。
    今回はそんな時に役に立つ統計情報を確認できるstat系コマンドについて解説する。

    ちなみに今回解説する内容はCentOS6.6でのものに限定し、他のディストリビューションで同じような解釈ができるかどうかは保証しません。

    vmstat

    vmstatのvmはvirtual memoryであり、仮想メモリの統計情報について表示することのできるプログラムである。

    manコマンドをかいつまんで概要を把握する。

    vmstatはプロセス、メモリ、ページング、ブロッキングIO、CPU使用率に関するレポート情報を表示する。
    最初のレポートに関しては最後に再起動された瞬間からの平均値を表示し、追加で表示されるレポートに関しては特定の時間を定めその間に抽出したデータを元に統計を表示する。
    プロセスとメモリの統計情報に関しては常にその時の状態が表示される。

    VMモードで表示される項目

  • Procs
  • r : 実行を待っているプロセスの数
    b : 割り込み不可能なスリープ状態にあるプロセスの数

  • Memory
  • swpd : 使用されている仮想メモリの総量
    free : 使用されていないメモリの総量
    buff : バッファとして使用されているメモリの総量(カネールバッファ)
    cache : キャッシュとして使用されているメモリの総量(ページキャッシュ)
    inact : 非アクティブなメモリの総量
    active : アクティブなメモリの総量

  • Swap
  • si : ディスクからスワップインされたメモリの総量
    so : ディスクへとスワップアウトされたメモリの総量

  • IO
  • bi : ブロックデバイスから取得した秒間ブロック数
    bo : ブロックデバイスへと送信した秒間ブロック数

  • System
  • in : 秒間の割り込み数
    cs : 秒間のコンテキストスイッチ数

  • CPU
  • us : user time. アプリケーションコードが消費した時間
    sy : system time. カーネルコードが消費した時間
    id : アイドル時間。linux 2.5.41以上ではio待ちも含む
    wa : io待ちの時間。linux 2.5.41以上ではidに含まれる
    st : linux2.6.11以上ではvirtual machineに消費した時間

    vmstatという名前の割にはわりかしCPUの状態からシステムのボトルネックの場所を把握することに用いられるように思う。

    iostat

    CPUの使用状況やデバイス、パーティション、NFSごとに入出力の統計情報を表示する。
    vmstatと比較するとCPUの情報が出力されるところは似たようなところがあるが、入出力に関しては複数のデバイスごとに表示が確認できるため
    より詳細なIOボトルネックを確認したい場合には有効に働きそうである。

    またちょっとmanコマンドの説明を確認する。
    デバイスごとにその平均転送レートから入出力状況についてモニタリングを行う。
    これによって物理的なディスクの入出力とバランスをとるためにシステムの設定を最適化することができる。
    iostatを実行して初めに表示される項目はシステムを再起動してからの統計情報を表示する。
    その後のそれぞれの出力に関しては前回のレポート集計後について集計を行う。

    特にvmstatから特筆して異なるような使い方はありません。

    mpstat

    これも同様にCPUの使用状況などをコア別に詳細に確認することができるようになります。
    vmstatやtopでもボトルネックが特定できなかった時などに使用すると良いと思います。

    と…

    dstat

    これ便利。ちなみにpythonで書かれている。
    iostat, vmstat両方で確認できる内容がほぼそのまま確認できます。
    しかも色付きだし、表示が見やすく整形されている。(vmstatはなんか表示崩れてて見難い)

    おまけに–top-cpuや–top-cputimeなどのオプションを使用することで、その瞬間での最もcpu使用率の高いプロセスなどが表示されます。
    エッジが立っているようなプロセスの監視を行いたいときなどは便利かも。

    まとめ

    さくっと使ってみた間隔としては
    とりあえずdstatで大雑把に見る。プロセス単位で見たい場合はやはりtopコマンドが良さそうです。