Monthly Archives: 2月 2014

GNU grep 2.18リリース: 10倍速くなったと思ったら今度は200倍遅くなっていた

先日の記事
いまさらgrepが10倍高速化したのはなぜか
が思わぬ閲覧数を稼いでしまい、トルコ語の知識を日本に広めるのに大きな貢献をしたような気がしますが、みなさんいかがお過ごしでしょうか。

実は先日の記事を書いた時にはすでに2.18がリリースされてたのだが、今回は2.17のときと違って日本の大手メディアが取り上げてなかったので、ついつい見落としていた。しかし実は2.18でも大きな変更が!!
Continue reading

サーバが死んだので設定変えてみた

昨日ママンサーバが死んだ。

昨日のポスト
いまさらgrepが10倍高速化したのはなぜか
が、思いのほうか人気が出てしまったようで、サーバの処理能力を超える負荷がかかったのが原因だ。まさかgrepの話でこんなに盛り上がるとは思っていなかった。はてなホッテントリに載ったあたりからアクセスが急増したようだ。

なので、ググって調べながら緊急対応を行った。以下にやったことを書こうと思う。

ここサイトは、DTIのVPSコース(一番安いやつ)+nginx+Wordpressで運用している。サーバの運用も慣れてないし、nginxを使うのも初めてなので、とにかくググりながら手探りで運用している。最初の設定はここに書いてあるとおり。

プロキシキャッシュの設定

これは昨日の夜にやった。

旧設定では、アクセスがあるたびにphpが実行されることになっているので効率が悪い。nginxにリバースプロキシを設定することで、よく最近参照されたコンテンツはphpを実行しなくてもキャッシュされているようにすることができる。

これについてはこのサイトが参考になった:
Nginx + WordPress proxy cache篇

詳しい仕組み等は上記リンクにかかれているので、省略して、設定ファイルだけさらしておく。(セキュリティの関係上一部編集してある)

/etc/nginx/nginx.convの一部:


http {
    include       /etc/nginx/mime.types;

    access_log	/var/log/nginx/access.log;

    sendfile        on;
    keepalive_timeout  65;
    tcp_nodelay        on;

    proxy_cache_path /var/cache/nginx/cache levels=1 keys_zone=zone:128m inactive=7d max_size=2g;
    limit_req_zone $binary_remote_addr zone=api:1m rate=1r/s;
    gzip  on;
    gzip_disable "MSIE [1-6]\.(?!.*SV1)";

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

ここでproxy_cache_pathで、キャッシュのディレクトリを指定している。もちろん、このディレクトリは作っておかないといけない。そして、limit_req_zoneで同一IPアドレスからの連続アクセス制限をしている。

/etc/nginx/sites-enabled/mysettingsの一部:


upstream backend {
    server 127.0.0.1:8000;
}

server {
    listen 80;
    listen   [::]:80 default ipv6only=on;

    server_name  hamukazu.com;
    access_log  /var/log/nginx/front.access.log;
    error_log  /var/log/nginx/front.error.log;
    location /  {
        proxy_pass http://backend;
        proxy_cache zone;
        proxy_cache_key $scheme$proxy_host$uri$is_args$args;
        proxy_cache_valid  200 1d;
    }
    location /wp-admin {
        proxy_pass http://backend;
    }
    location /wp-login.php {
        proxy_pass http://backend;
    }
}
 
server {
    listen 8000;

    access_log  /var/log/nginx/hamukazu.access.log;
    error_log  /var/log/nginx/hamukazu.error.log;
    root  /var/www/wp;
    location /  {
        index index.php;
        if (-f $request_filename) {
            expires 30d;
            break;
        }
        if (!-e $request_filename) {
            rewrite ^.*(/wp-.*) $1 last;
            rewrite ^(.*) /index.php?q=1 last;
        }
    }
    location ~ \.css {
        add_header  Content-Type    text/css;
    }
    rewrite /wp-admin$ $scheme://$host$uri/ permanent;
    location ~ \.php$ {
        include /etc/nginx/fastcgi_params;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass_header "X-Accel-Redirect";
        fastcgi_pass_header "X-Accel-Expires";
    }
}

ここでは、80番ポートにきたリクエストをキャッシュして、8000番ポートに投げている。/wp-adminと/wp-login.phpだけは例外で、キャッシュしない設定になってる。(そうしないとクッキーの不正でログインできない)

CDNの設定

今朝、このサイトを教えてもらい、CloudFlareという無料で使えるCDNがあるというので、早速登録してみた。

おかげでちょっと寝不足。すべては太陽のせいだ。

いまさらgrepが10倍高速化したのはなぜか

最近GNU grepコマンドの最新バージョンがリリースされ、速度が10倍になったとのアナウンスがあった。それを聞いて、なんであんな枯れた技術に10倍もの高速化の余地があったのだろうと不思議に思った人も多いだろう。
ニュース記事:grepコマンド最新版、”-i”で10倍の高速化
本家のリリースノート:grep – News: grep-2.17 released [stable]

今回のリリースでは正確には、マルチバイトロケールで、-iオプション(–ignore-case、つまり大文字小文字を区別しないオプション)をオンにした時の速度が10倍くらいになったそうだ。

なぜそんなに速くなったのか?逆を言えば今までなぜそんなに遅かったのか?

そもそも、多くの日本人にとって「大文字小文字の区別」というと英語のアルファベットか、せいぜいフランス語とかドイツ語とかのアクサン記号・ウムラウトがついたものくらいしか思いつかないのかもしれないが、世の中にはもっと複雑なものがあるのが原因のようだ。

対応するgitのコミットはこれらしい:
grep: make –ignore-case (-i) faster (sometimes 10x) in multibyte locales

ここのソースコード内のコメントに詳しい解説が書いてあった。なぜ今まで遅かったのかが書いてある。

引用:
+ /* As currently implemented, case-insensitive matching is expensive in
+ multi-byte locales because of a few outlier locales in which some
+ characters change size when converted to upper or lower case. To
+ accommodate those, we revert to searching the input one line at a
+ time, rather than using the much more efficient buffer search.
+ However, if we have a regular expression, /foo/i, we can convert
+ it to an equivalent case-insensitive /[fF][oO][oO]/, and thus
+ avoid the expensive read-and-process-a-line-at-a-time requirement.
+ Optimize-away the “-i” option, when possible, converting each
+ candidate alpha, C, in the regexp to [Cc]. */

これはつまりこういうことだ。

  • マルチバイトロケールによっては、「大文字→小文字」または「小文字→大文字」の変換をすると文字のバイト数が変わってしまうことがある(!)
  • そのため、バッファを使った効率の良い検索が使えず、1行読み込んで変換して検索するという効率の悪い実装が行われていた
  • そこで発想を変えて、検索される文字列を同値な正規表現にあらかじめ変換すると速くなった。つまり「foo」という文字列を検索するときは「[fF][oO][oO]」という正規表現に変換してから検索する。

大文字と小文字でバイト数が違うことがあるというのは知らなかったし驚いた。なんか具体例を示せないかなと思ったのだが、このgrepのコミットにはトルコ語でテストしたと書いてある。トルコ語関係で検索したら、マイクロソフトの次のようなサイトが見つかった。

カスタムの大文字と小文字の対応規則および並べ替え規則

引用:
トルコ語のアルファベットに固有の大文字と小文字の対応規則は、言語によっては大文字と小文字の対応が一部異なる例を示しています。 ほとんどのラテン語アルファベットでは、文字 “I” (Unicode 0069) は文字 “I” (Unicode 0049) の小文字です。 それに対し、トルコ語のアルファベットでは文字 “I” に 2 つのバージョンがあります。1 つはドット付きで、もう 1 つはドットなしです。 トルコ語では、文字 “I” (Unicode 0049) は別の文字 “I” (Unicode 0131) の大文字と見なされます。 文字 “I” (Unicode 0069) は、さらに別の文字 “İ” (Unicode 0130) の小文字と見なされます。 このため、大文字と小文字を区別せずに、文字 “I” (Unicode 0069) と “I” (Unicode 0049) の文字列比較を行った場合、ほとんどのカルチャでは成功しますが、トルコ語 (トルコ) のカルチャ “tr-TR” では失敗します。

これ、ざっと読んだだけでは、何を言っているのかわからなかったのだが、改めてまとめると以下のようになる。

  • 英語を含む多くの言語では”i”は”I”の小文字だとみなされる
  • トルコ語では”i”と”I”、両方の文字とも使われている
  • しかしトルコ語では”i”は”I”の小文字ではない
  • トルコ語では、”i”はある他の文字の小文字で、”I”はある他の文字の大文字である

そこで、このサイトを使って調べてみたところ、トルコ語でIの小文字はı(UTF-8でc4 b1)、iの大文字はİ(UTF-8でc4 b0)となっている。Iとiは1バイトなので、それらをトルコ語でそれぞれ小文字と大文字に変換すると2バイトになってサイズが変わることになる。

まとめ

  • 世界には、大文字/小文字の変換を行うとUTF-8で表現した時のサイズが変わる言語がある
  • そのせいで、従来のGNU grepは1行ずつ読み込んで変換するという効率の悪い実装をしていたが、検索文字列を同値な正規表現に変換することで高速化を実現した
  • 文字列の検索は枯れた技術に思われているかもしれないが、UTF-8周りだといろいろ気持ち悪いことがあるようだ(枯れているのは「バイト列」の検索?)

P.S. 英語、トルコ語混じりファイルの検索で、トルコ人が苦労することはないんだろうかと思った。

追記:
(2014/2/27 11:37 加筆)
その後、バージョン2.18についての解説も書いたので、ここにリンクを貼っておく。
GNU grep 2.18リリース: 10倍速くなったと思ったら今度は200倍遅くなっていた

「リケジョ」という言葉について私見

先に結論を言っておこうと思います。「リケジョ」って単語自体は現時点では差別語ではないと思います。しかしこのままま行くと将来差別語になってしまうのではと危惧しています。

例の小保方さんの研究成果に関係して、「リケジョ」は差別語じゃないかという話がネット上で賑わっているようなので、時流に乗ってみようと思いました。

震源地は、めいろまさんこと谷本真由美さんのこの記事のようですね:
一晩中泣き明かした30歳若手女性研究者と書く我が国にはゴシップ新聞しかないらしい

ここでは小保方さんの研究成果の報道のあり方を批判しており、途中、『「リケジョ」という差別用語』というフレーズが繰り返し使われてます。

一方、これについて反論めいたものとして片岡英彦さんのコラム:
「リケジョ」は本当に差別用語なのか?「リケダン」や「草食男子」はどうかと、あえて言ってみる

ここでは、大学での取り組みや講談社「Rikejo」マガジンなどの例をあげながら、「リケジョ」というのはもともと悪い意味ではないと主張しています。

この議論、なんかかみ合っていないような気がするのは私だけでしょうか。一昔前の言葉狩り論争を思い出してしまいました。

結局どの単語がいけないとかという話ではなくて、その使われる文脈の話でしょう。

めいろまさんの指摘の通り、マスコミの報道の中には、端々に女性なのにすごいというような性差別的なニュアンスが含まれているものが多く、そういう報道とセットで、わかりやすい象徴的な単語として「リケジョ」というのが使われている。「リケジョ」という言葉にもともと差別的な意味はなかったとしても、そういうのが続くとどうしても差別的な印象と重なってしまう。

一方で、日本では女性の理系研究者が少ないので、積極的に育てましょう、女子中高生に理系に興味を持ってもらいましょう、という活動には大賛成ですし。そこでキャッチフレーズ的に「リケジョ」という単語を使うことは全く差別とは思わないです。広報活動として妥当な言葉の選択ではとも思います。

なので、私の意見をまとめると。

  • 「リケジョ」という単語自体は現在のところ差別語とは思わない。しかし、そのマスコミでの使われ方に問題がある。
  • 女子中高生にもっと理系に興味を持ってもらおうという取り組みには賛成。その取り組みをわかりやすくするために「リケジョ」という言葉を使うことにも賛成。
  • 現在差別語でなかったとしても差別的な文脈と同時に使われることが増えるとイメージがどんどん悪くなる。過去にも、差別語というのはそうやってできていったのではないか。

以上、優秀な「リケジョ」のみなさんに敬意をはらいつつ…