admin のすべての投稿

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コマンドが良さそうです。


    mysqlにおけるvarcharのindex使用時の注意

    概要

    みなさんmysqlを使用していてvarcharのカラムにindexをつけることってないだろうか?
    selectする際にindexが付いているカラムを対象にwhereをつけているのにindexが有効になっていない機会があり得る。
    今回はそんな時に認識しておかないと、はまるかもしれない挙動について取り上げる。
    (そもそもvarcharにindexつけることについてはまた別の機会にでも)

    準備

  • テーブル
  • varcharのカラムにindex付いているテーブルを用意

  • 確認方法
  • 確認方法としてはmysqldの設定としてINDEXの効いていないクエリがslowlogに出力されるように設定し、slowlogの出力を確認しながら進める。

    事前確認

    まずは普通にqueryを発行してみる

  • 数値として検索した場合
  • mysql> select count(*) from t1 where column1 = 7395584;
    +----------+
    | count(*) |
    +----------+
    |       37 |
    +----------+
    1 row in set (0.63 sec)
    
    # Time: 150129  5:47:34
    # User@Host: mysql[mysql] @ localhost []
    # Query_time: 0.633334  Lock_time: 0.000088 Rows_sent: 1  Rows_examined: 1264725
    SET timestamp=1422510454;
    select count(*) from t1 where column1 = 7395584;
    
  • 文字列として検索した場合
  • mysql> select count(*) from t1 where column1 = '7395584';
    +----------+
    | count(*) |
    +----------+
    |       37 |
    +----------+
    1 row in set (0.01 sec)
    

    早い。slowlogの出力はなし。

  • explain
  • explainすると数値として検索した場合はいわゆるフルスキャンとなっていることがわかる

    mysql> explain select count(*) from t1 where column1 = 7395584;
    +----+-------------+--------+-------+---------------+------+---------+------+---------+--------------------------+
    | id | select_type | table  | type  | possible_keys | key  | key_len | ref  | rows    | Extra                    |
    +----+-------------+--------+-------+---------------+------+---------+------+---------+--------------------------+
    |  1 | SIMPLE      | t1     | index | idx1          | idx1 | 396     | NULL | 1265625 | Using where; Using index |
    +----+-------------+--------+-------+---------------+------+---------+------+---------+--------------------------+
    1 row in set (0.00 sec)
    
    mysql> explain select count(*) from t1 where column1 = '7395584';
    +----+-------------+--------+------+---------------+------+---------+-------+------+--------------------------+
    | id | select_type | table  | type | possible_keys | key  | key_len | ref   | rows | Extra                    |
    +----+-------------+--------+------+---------------+------+---------+-------+------+--------------------------+
    |  1 | SIMPLE      | t1     | ref  | idx1          | idx1 | 386     | const |   37 | Using where; Using index |
    +----+-------------+--------+------+---------------+------+---------+-------+------+--------------------------+
    1 row in set (0.00 sec)
    

    と、生のmysqlクライアントからの検索は分かった。
    アプリケーションからの実行はどうなるんだろうか。
    使用する機会の多いpdoについて検証してみる。

    アプリケーションレイヤからの検証

  • 生のquery
  • 簡単なphpアプリケーションを作成して、slowlogの出力を見る。

    <?php
    $dns = "mysql:host=localhost;dbname=testdb;charset=utf8";
    $user = "mysql";
    $pass = "pass";
    $pdo = new PDO($dns, $user, $pass);
    
    $stmt = $pdo->query("SELECT count(*) AS cnt FROM t1 WHERE column1 = 7395584");
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    
    var_dump($row);
    

    出力は下記のようになった。きちんと動作はしている。

    [vagrant@local tmp]$ php select.php 
    array(1) {
      ["cnt"]=>
      string(2) "37"
    }
    

    さてslowlog出力は?

    # Time: 150129  5:55:12
    # User@Host: mysql[mysql] @ localhost []
    # Query_time: 0.242804  Lock_time: 0.000055 Rows_sent: 1  Rows_examined: 1264725
    SET timestamp=1422510912;
    SELECT count(*) AS cnt FROM t1 WHERE column1 = 7395584;
    

    でた!
    これに関してはpdoは特に何もしない。mysqlクライアントから直接クエリを入力した時と同じような結果となった。
    結果は割愛するが、シングルクォートで文字列を囲った時にはslowlogの出力はされなくなった。したがってindexによる検索が有効になっているようである。

    ここで勘の良い人は気になっているかもしれないが、では逆に数値のカラムにINDEXがはられている場合、where句に記述したデータをシングルクォートでくくり文字列として検索した場合どうなるのか。
    ということで気になっているであろうが、これに関して検証した結果どちらでも適切にindexが効くことが確認できた。

  • prepared statement
  • さてprepared statementを利用した場合はどのようになるのか、検証したいと思う。
    pdoにはbindを行うために二種類のインタフェースが存在するが、今回はbindValueを取り上げる。
    マニュアルを確認すると第三引数にバインドするパラメータの方が指定できる。なんとなく今検証していることをの結果を匂わせるようなインタフェースをしている。

    まずは第三引数のことはおいておき、純粋にPHPの型推論による挙動の変化を観察しようではないか。

    初めに数値としてbindValueを実行してアプリケーションを叩いてみる。
    アプリケーションは下記のようになる。

    <?php
    $dns = "mysql:host=localhost;dbname=testdb;charset=utf8";
    $user = "mysql";
    $pass = "pass";
    $pdo = new PDO($dns, $user, $pass);
    
    $sql = "SELECT count(*) AS cnt FROM t1 WHERE column1 = :column1";
    $stmt = $pdo->prepare($sql);
    $stmt->execute(array('column1' => 7395584));
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    
    var_dump($row);
    

    動作を確認し、結果も正常に取得できている。

    [vagrant@local tmp]$ php select2.php 
    array(1) {
      ["cnt"]=>
      string(2) "37"
    }
    

    さて、肝心なslowlogはどうなっているかというと。
    出ていない!
    prepared statementとbindを利用すると、数値文字列などのギャップはpdo側で吸収してくれるようだ。

    また当然bindの引数の型を文字列に加工したとしてもslowlogは出力されずに、indexが有効になっていることが確認できた。

    これについて思うことは、phpの場合に限って言うとなるべくprepared statementを使用したほうがお馬鹿なミスが減るだろうということだ。
    生のsqlは思ったよりもずっとデリケートなのかもしれない。

    他のアプリケーションに関してもアプリケーションが提供してくれる機能を用いるほうが良いだろう。


    macのtopコマンドを追う

    家に帰ると充電器に指しておいた愛器のMACがファンを最高スピードまでクロックアップさせながら唸りをあげていた。

    なぜだ。蓋閉じてるのに。

    今回はこういった漠然とした状態からコンピュータ内部でざっくり何が起こっているか判別するときに使用できるtopコマンドを掘り下げて解説しよう。

    topコマンドはosx(mac)だけでなくunix実装のほとんどのディストリビューションで提供されているツールであろう。
    システム全体をざっくり見るときによく用いられる。
    ただこれディストリビューションによって確認できる情報や、オプションとか結構変わってくるので注意。

    さてmacでterminalを立ち上げtopコマンドを打鍵すると下記のような表示が見て取れると思う。

    Processes: 226 total, 4 running, 9 stuck, 213 sleeping, 1096 threads
    Load Avg: 3.12, 3.16, 2.93  CPU usage: 16.81% user, 23.22% sys, 59.95% idle  SharedLibs: 9200K resident, 14M data, 0B linkedit.
    MemRegions: 45374 total, 1842M resident, 59M private, 1496M shared. PhysMem: 5103M used (1685M wired), 2594M unused.
    VM: 538G vsize, 1066M framework vsize, 3072256(54) swapins, 3399825(0) swapouts.  Networks: packets: 6042458/6896M in, 3543389/504M out.
    Disks: 1397033/93G read, 1239425/75G written.
    

    Load Avg: 3?何も起動してないのに。。。

    表示される項目について解説する。

    Processes – total

    マシン上で動作しているプロセスの数

    Processes – running

    実行中プロセスの数
    実行中となりうるプロセス数は1CPUにつき1プロセスだけである。
    動作しているマシンのCPUがクアッドコアなため最大で同時に4つのプロセスが動作可能

    Processes – stuck

    そもそもstuckとは?
    osxのtopコマンドにおけるstuckとはプロセスの状態がLIBTOP_STATE_STUCKとなっている状態のプロセス数である。
    またカーネルの状態としてはTH_STATE_UNINTERRUPTIBLEであることを指す。
    これはプロセスが割り込み不可能なwait状態であることを指す。
    通常はディスクやネットワークに対するI/O待ちのような状態が該当する。

    Processes – sleeping

    wait状態のプロセスの数を表す。

    Processes – threads

    スレッドの数を表す。

    Load Avg

    ロードアベレージとは実行キューの中に入っている平均ジョブ数を表す。
    (これはosxの定義であってディストリビューションによって算出方法は多少異なることもある)
    3つの数値が並んでいるがこれは左から、1分平均、5分平均、15分平均を表す。

    CPU usage – user

    ユーザ実行時間を表す。ユーザ時間とはアプリケーションレイヤでカーネル処理(システムコール)に費やされている時間以外の時間のことを指す。
    例えばアプリケーション中でシステムコールを利用している場合、その間はシステム時間として認識される。

    CPU usage – system

    システム実行時間を表す。アプリケーションやOSによりシステムコールに費やされている時間を表す。

    CPU usage – idle

    アイドル時間を表す。

    SharedLibs – resident

    メモリに常駐している共有メモリを表す。

    SharedLibs – data

    データ領域を表す。

    SharedLibs – linkedit

    MemRegions – total

    使用メモリサイズ。単位はmach virtual memory。

    MemRegions – resident

    常駐メモリのサイズ

    MemRegions – private

    非共有メモリのサイズ

    MemRegions – shared

    共有メモリのサイズ

    PhysMem – used

    使用中の物理メモリサイズ
    wiredという表記があるが、これはos kernelによって使用されていることを意味する。

    PhysMem – unused

    未使用な物理メモリサイズ

    VM – vsize

    仮想メモリの総サイズ。
    仮想メモリとは実際には存在しないが、実メモリにマップ領域を用意し、実際のメモリ上にページを配置したり(スワップイン)
    抱えきれなくなったページをディスク上に吐き出したり(スワップアウト)することでプロセスからは膨大なメモリが使用可能なように見えるようにする仕組み。
    当然実メモリよりも大幅に大きなサイズとなる。

    VM – framework vsize

    共有メモリにより諸費される仮想メモリサイズ。

    VM – swapins

    スワップインを起こしたページ数

    VM – swapouts

    スワップアウトを起こしたページ数

    Network packets

    ネットワークに対するin/outのパケットサイズを表す。

    Disks

    ディスク装置に対するread/writeのデータサイズを表す。

    本日はこんなところで。にしてもosxはドキュメントが少ないですね。
    https://apple.stackexchange.com/
    上記はstackoverflowのapple版みたいなものなんですが、そちらが一番情報量が豊富なように思います。


    count(*)からinnodbにおけるindex構成を確認する

    * 概要

    今回はinnodbにおけるcountの高速化について検証する。

    きっかけは下記のブログですが。いつもお世話になっております。

    http://nippondanji.blogspot.jp/2010/03/innodbcount.html

    要約すると下記のようなスキーム雨がある時

    CREATE TABLE t1 (  
      a bigint(20) unsigned NOT NULL AUTO_INCREMENT,  
      b int(11) DEFAULT NULL,  
      c tinyint(4) DEFAULT NULL,  
      d date DEFAULT NULL,  
      e varchar(200) DEFAULT NULL,  
      f varchar(200) DEFAULT NULL,  
      g varchar(200) DEFAULT NULL,  
      h varchar(200) DEFAULT NULL,  
      i varchar(200) DEFAULT NULL,  
      PRIMARY KEY (a)  
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  
    

    下記のようなsqlを想定する

    SELECT count(*) FROM t1;
    

    このとき例えばtinyintなどにindexを貼ることで、count(*)の高速化が見込める。

    innodbのcount(*)において全レコードへのアクセスが必要になることは変わりないが
    これは主キー(bigint)を全走査することよりも、小さいindexを全走査するほうが効率が良いということである。

    まあ頭のなかでは理解できて、予想はできているんだけどちゃんと自分の手でピコピコやりたいなというところで下記を確認する。
    1. 検索速度がa,b,cで変わることを確認(参照テーブルも)
    2. e,f,g,h,iがあるときとないときで検索速度がそこまで変わらないことの確認(クラスタインデックスのノードが影響を与えないこと)

    * 検証

    検証2のリーフノードの大きさの検証のため下記データベースを用意する。

    CREATE TABLE t1 (  
      a bigint(20) unsigned NOT NULL AUTO_INCREMENT,  
      b int(11) DEFAULT NULL,  
      c tinyint(4) DEFAULT NULL,  
      d date DEFAULT NULL,  
      e varchar(200) DEFAULT NULL,  
      f varchar(200) DEFAULT NULL,  
      g varchar(200) DEFAULT NULL,  
      h varchar(200) DEFAULT NULL,  
      i varchar(200) DEFAULT NULL,  
      PRIMARY KEY (a)  
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  
    
    CREATE TABLE t2 (
      a bigint(20) unsigned NOT NULL AUTO_INCREMENT,  
      b int(11) DEFAULT NULL,  
      c tinyint(4) DEFAULT NULL,  
      PRIMARY KEY (a)  
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  
    

    下記みたいな感じでデータを作成する

    <?php
    
    $pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8','root','');
    $stmt = $pdo->prepare("INSERT INTO t1 (b,c,d,e,f,g,h,i) VALUES (:b,:c,:d,:e,:f,:g,:h,:i)");
    
    $bind = array();
    $bind['b'] = $i;
    $bind['c'] = 1;
    $bind['d'] = '2014-12-25';
    $bind['e'] = '1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890';
    $bind['f'] = '1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890';
    $bind['g'] = '1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890';
    $bind['h'] = '1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890';
    $bind['i'] = '1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890';
    
    foreach($bind as $key => $value){
     $stmt->bindValue($key, $value);
    }
    
    for($i=1; $i<=10000000; $i++) {
     $stmt->execute();
    }
    
    // 値同じだけ今回はcountなので影響なし。
    

    * sqlのクエリキャッシュを無効にする

    mysql> show variables like 'query_%';
    +------------------------------+---------+
    | Variable_name                | Value   |
    +------------------------------+---------+
    | query_alloc_block_size       | 8192    |
    | query_cache_limit            | 1048576 |
    | query_cache_min_res_unit     | 4096    |
    | query_cache_size             | 0       |
    | query_cache_type             | OFF     |
    | query_cache_wlock_invalidate | OFF     |
    | query_prealloc_size          | 8192    |
    +------------------------------+---------+
    7 rows in set (0.00 sec)
    

    * 検索する

    まずは2.について。リーフノードのデカさは検索性能に影響するのか。
    予想ではしないと思う。B木のキーノードだけ走査するわけだから。

    mysql> explain select count(*) from t1;
    +----+-------------+-------+-------+---------------+---------+---------+------+---------+-------------+
    | id | select_type | table | type  | possible_keys | key     | key_len | ref  | rows    | Extra       |
    +----+-------------+-------+-------+---------------+---------+---------+------+---------+-------------+
    |  1 | SIMPLE      | t1    | index | NULL          | PRIMARY | 8       | NULL | 9140175 | Using index |
    +----+-------------+-------+-------+---------------+---------+---------+------+---------+-------------+
    1 row in set (0.00 sec)
    
    mysql> desc t1;
    +-------+---------------------+------+-----+---------+----------------+
    | Field | Type                | Null | Key | Default | Extra          |
    +-------+---------------------+------+-----+---------+----------------+
    | a     | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
    | b     | int(11)             | YES  |     | NULL    |                |
    | c     | tinyint(4)          | YES  |     | NULL    |                |
    | d     | date                | YES  |     | NULL    |                |
    | e     | varchar(200)        | YES  |     | NULL    |                |
    | f     | varchar(200)        | YES  |     | NULL    |                |
    | g     | varchar(200)        | YES  |     | NULL    |                |
    | h     | varchar(200)        | YES  |     | NULL    |                |
    | i     | varchar(200)        | YES  |     | NULL    |                |
    +-------+---------------------+------+-----+---------+----------------+
    9 rows in set (0.00 sec)
    
    mysql> select count(*) from t1;
    +----------+
    | count(*) |
    +----------+
    | 10000000 |
    +----------+
    1 row in set (15.87 sec)
    
    mysql> explain select count(*) from t2;
    +----+-------------+-------+-------+---------------+---------+---------+------+---------+-------------+
    | id | select_type | table | type  | possible_keys | key     | key_len | ref  | rows    | Extra       |
    +----+-------------+-------+-------+---------------+---------+---------+------+---------+-------------+
    |  1 | SIMPLE      | t2    | index | NULL          | PRIMARY | 8       | NULL | 9223787 | Using index |
    +----+-------------+-------+-------+---------------+---------+---------+------+---------+-------------+
    1 row in set (0.00 sec)
    
    mysql> desc t2;
    +-------+---------------------+------+-----+---------+----------------+
    | Field | Type                | Null | Key | Default | Extra          |
    +-------+---------------------+------+-----+---------+----------------+
    | a     | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
    | b     | int(11)             | YES  |     | NULL    |                |
    | c     | tinyint(4)          | YES  |     | NULL    |                |
    +-------+---------------------+------+-----+---------+----------------+
    3 rows in set (0.00 sec)
    
    mysql> select count(*) from t2;
    +----------+
    | count(*) |
    +----------+
    | 10000000 |
    +----------+
    1 row in set (1.98 sec)
    

    !驚愕である。リーフノードの大きさが検索性能に大きく依存しているではないか。
    これについてはもっと深追いして理解する必要がありそうだ。

    * b (int)にindexを追加

    mysql> ALTER TABLE t1 ADD INDEX idx_int(b);
    Query OK, 0 rows affected (33.17 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    mysql> explain select count(*) from t1;
    +----+-------------+-------+-------+---------------+---------+---------+------+---------+-------------+
    | id | select_type | table | type  | possible_keys | key     | key_len | ref  | rows    | Extra       |
    +----+-------------+-------+-------+---------------+---------+---------+------+---------+-------------+
    |  1 | SIMPLE      | t1    | index | NULL          | idx_int | 5       | NULL | 9140175 | Using index |
    +----+-------------+-------+-------+---------------+---------+---------+------+---------+-------------+
    1 row in set (0.00 sec)
    
    mysql> select count(*) from t1;
    +----------+
    | count(*) |
    +----------+
    | 10000000 |
    +----------+
    1 row in set (1.92 sec)
    

    疾いっ!

    * つづいてc (tinyint)にindexを追加

    mysql> ALTER TABLE t1 ADD INDEX idx_tinyint(c);
    
    Query OK, 0 rows affected (34.54 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    mysql> explain select count(*) from t1;
    +----+-------------+-------+-------+---------------+-------------+---------+------+---------+-------------+
    | id | select_type | table | type  | possible_keys | key         | key_len | ref  | rows    | Extra       |
    +----+-------------+-------+-------+---------------+-------------+---------+------+---------+-------------+
    |  1 | SIMPLE      | t1    | index | NULL          | idx_tinyint | 2       | NULL | 9140175 | Using index |
    +----+-------------+-------+-------+---------------+-------------+---------+------+---------+-------------+
    1 row in set (0.00 sec)
    
    mysql> select count(*) from t1;
    +----------+
    | count(*) |
    +----------+
    | 10000000 |
    +----------+
    1 row in set (1.78 sec)
    

    あんまりintと変わらない。これも参考元にある通り。
    だが内部的には読み込みページとかの量が半減しているはずである。

    さて予想外の挙動を見せたクラスタインデックスの違いはなんだろうか。

    ここでt1のvarcharで表されるカラムに対してもゴミを投入していたことに着目する具体的には下記のような感じで全レコードに対して同じようなデータを投入している
    検証のために下記のようなテーブルt6を作成した

    CREATE TABLE <code>t6</code> (
      <code>a</code> bigint(20) unsigned NOT NULL AUTO_INCREMENT,
      <code>b</code> int(11) DEFAULT NULL,
      <code>c</code> tinyint(4) DEFAULT NULL,
      <code>d</code> date DEFAULT NULL,
      <code>e</code> varchar(200) DEFAULT NULL,
      <code>f</code> varchar(200) DEFAULT NULL,
      <code>g</code> varchar(200) DEFAULT NULL,
      PRIMARY KEY (<code>a</code>)
    ) ENGINE=InnoDB AUTO_INCREMENT=20000001 DEFAULT CHARSET=utf8;
    

    これに対してデータを1000万件投入する。その時のデータと速度は下記のようになっている

    mysql> select * from t6 limit 1;
    +----------+-----------+------+------------+------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+------+
    | a        | b         | c    | d          | e                                                                                                    | f                                                                                                    | g    |
    +----------+-----------+------+------------+------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+------+
    | 10000001 | 100000000 |    1 | 2014-12-25 | 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 | 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 |      |
    +----------+-----------+------+------------+------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+------+
    1 row in set (0.00 sec)
    
    mysql> select count(*) from t6;
    +----------+
    | count(*) |
    +----------+
    | 10000000 |
    +----------+
    1 row in set (10.42 sec)
    

    リーフノードのデータ量が検索速度に影響を及ぼす可能性を検証するためにカラムgを空文字にupdateして検証する。

    mysql> update t6 set g = '';
    Query OK, 10000000 rows affected (1 min 55.63 sec)
    Rows matched: 10000000  Changed: 10000000  Warnings: 0
    
    mysql> alter table t6 engine innodb;
    Query OK, 10000000 rows affected (59.86 sec)
    Records: 10000000  Duplicates: 0  Warnings: 0
    
    mysql> select count(*) from t6;
    +----------+
    | count(*) |
    +----------+
    | 10000000 |
    +----------+
    1 row in set (6.58 sec)
    

    なんと!高速化されたではないか。
    正直今までcount(*)するときにindexのキーだけなめて検索しているのかと思っていた。(これはcount(id)でも計測時間が変わらなかったことからの推測でもある)
    しかしこの結果から導かれるのは、count(*)したときにリーフノードのデータを読み取っているということにほかならないのではないかと。

    B木インデックスってのはB木のキーと値を全部読み取るような振る舞いをしている。と仮定できる。