2012/12/04


以前、こんな記事を書いた。
Big Sky :: 日本語grepが出来るjvgrepというのを作った。
http://mattn.kaoriya.net/software/lang/go/20110819203649.htm
実は jvgrep を作った当初、処理がかなり遅かった。まぁ複数のエンコーディングを試すからしょうがないよね程度に思ってたけど、どうにか速くならないかと思い、処理の並行化を行ってパフォーマンスを向上させた。この記事はその時にやった改善策。

jvgrep は -R オプションや **/* で再帰検索する機能が付いているんだけど、これを行う場合
  • find
  • grep
という処理が走る事になる。 しかしながら結果の順番を守ろうと考えた場合、find と grep を安直に同時に走らせる訳にはいかなくなる。走らせると結果が交錯してしまうからだ。
こういうのを行う場合、C言語だとFIFOキューとスレッドを作り、find 側が push、grep 側が pop を行う仕組みを作る。
しかしながらメモリの増加を管理したり、grep 側が空きになった時に待機する処理ってのを考えると、C言語だと結構めんどくさかったりする。

Go言語はこのあたりが非常に簡単に実装出来る様になっている。

まず find 部と grep 部の処理を分割し、grep 部を FIFO キューに対して連続で呼び出せる様にする。 func GoGrep(ch chan *GrepArg, done chan int) {
    for {
        arg := <-ch
        if arg == nil {
            break
        }
        Grep(arg)
    }
    done <- 1
}
Grep が本体だが今回の話の本質ではないので省略。引数の ch に Grep が使う引数が飛び込んで来る。全ての grep 対象が完了するか途中終了する場合には find 側から nil を渡すというお約束にした。
メインコントローラ側で、この FIFO となるチャネルを作る。 ch := make(chan *GrepArg)
done := make(chan int)
なぜ二つチャネルを作っているかというと、find 側が先に終了してしまった場合にプログラムが終了しない様、待機する為で、上記の GoGrep の最後に 1 を渡している。 チャネルを作ったら GoGrep をバックグラウンドで起動する。 go GoGrep(ch, done)
Go言語は関数呼び出しに go を付けるだけで非同期に実行してくれる。
次に find 部。Go言語の filepath パッケージにはファイルが見つかる度にコールバック関数を呼び出してくれる Walk がある。
filepath.Walk(root, func(path string, info os.FileInfo, err errorerror {
    if info == nil {
        return err
    }

    // 以下 path に対する処理を行う。
    path = filepath.ToSlash(path)

    // 検索対象外のパスにマッチする場合
    if ere != nil && ere.MatchString(path) {
        // ディレクトリならば SkipDir を返して他のフォルダに移る
        if info.IsDir() {
            return filepath.SkipDir
        }
        // ファイルならば以下の処理は行わない
        return nil
    }

    // ... 省略 ...

    // 検索対象のファイルならば
    if fre.MatchString(path) {
        // GoGrep が待っているチャネルにパラメータを渡す
        ch <- &GrepArg{pattern, path, /* ... 省略 ... */}
    }
})
検索対象のファイルが見つかった場合、先ほど作成した ch に Grep 引数を渡している。
find 側は全てのファイルを検索し終えたら grep 側に処理の終了を伝える。 ch <- nil
そして grep の終了(done)を待つ。 <-done
図解すると
jvgrep
こんなイメージになる。find もフォルダを再帰的に検索すると結構重くなるので各処理を並行実行させる事でかなりのパフォーマンス向上が得られた。

最初から find 部と grep 部が関数分けされていれば、もっと簡単に並行処理の実装が出来た事になる。
Posted at by



2012/11/28


grep の -o オプションを使うとマッチした部分だけ列挙してくれる。
例えば <div>foo</div><div>bar</div>
<div>baz</div>
というテキストファイル foo.txt に対して grep -o "<div[^>]*>[^<]*<\/div>" foo.txt
と実行すると <div>foo</div>
<div>bar</div>
<div>baz</div>
が得られる。これを応用して echo 餃子の王将 | grep -o .
とやると




が得られる。

便利!!
Posted at by



2012/11/26


いままでvimプラグインの管理には pathogen を使ってきたんだけど
tpope/vim-pathogen - GitHub

pathogen.vim: manage your runtimepath

https://github.com/tpope/vim-pathogen
unbundle を使う事にした。
Vim script management system - The Terminal Programmer

22 November 2011: I squashed my entire Vim configuration history while retaining the three branch ar...

http://snk.tuxfamily.org/log/vim-script-management-system.html
sunaku/vim-unbundle ツキ GitHub

Activates Vim scripts (bundles) from isolated directories

https://github.com/sunaku/vim-unbundle
原理的には pathogen と一緒。ただし pathogen は bundle 配下のプラグインを全てロードしてしまう。なので C++ やる時でも jedi-vim をロードしてしまうし、perl やる時でも clang_complete をロードしてしまうし、python やる時でも perlomni をロードしてしまう。
結構無駄なメモリ使ってるなーと思ったので、unbundle で管理する事にした。
bundle ディレクトリ内のファイルタイプに依存したプラグインフォルダをごそっと以下の様に ftbundle ディレクトリ移動した。 +- ftbundle
| +-cpp
| |  +-clang_complete
| |
| +-javascript
| |  +-jscomplete-vim
| |  +-jslint.vim
| |  +-vim-javascript
| |
| +-jsx
| |  +-jsx.vim
| |
| +-lisp
| |  +-slimv
| |
| +-markdown
| |  +-vim-markdown
| |
| +-perl
| |  +-perlomni.vim
| |  +-vim-perl
| |
| +-python
| |  +-jedi-vim
| |
| +-slim
|     +-vim-slim
|
+- bundle
  いままでのプラグイン...
計ってないけど、起動の体感速度があがった気がする。なお unbundle には明示的にロードするコマンドも用意されている。しばらく使おうと思う。あと問題があったらパッチも書くかな。

P.S. sugyan さん、ももクロ紅白出場おめでとうございます。
Posted at by