2005年11月24日
Boostの正規表現クラスを使う
Boost の正規表現クラスを使ってみよう思い、ディレクトリ以下のファイルに対して再帰的に grep を行うコードを書いてみました。
以下がそのコードです。再帰的な grep はGNU grep の --recursive オプションでできるので実用的な意味はありません。
#include <assert.h> #include <unistd.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #include <boost/bind.hpp> #include <boost/filesystem/operations.hpp> #include <boost/filesystem/path.hpp> #include <boost/regex.hpp> using namespace std; namespace fs = boost::filesystem; class grep { public: grep(const fs::path& directory, const string& pattern) : directory_(directory), regex_(pattern) {} void perform() const { traverse(directory_, boost::bind(&grep::grep_file, this, _1)); } private: const fs::path directory_; const boost::regex regex_; template <typename visitor> void traverse(const fs::path& directory, const visitor& func) const { fs::directory_iterator end; for (fs::directory_iterator it(directory); it != end; ++it) { if (fs::is_directory(*it)) { traverse(*it, func); } else { func(*it); } } } void grep_file(const fs::path& path) const { const int fd = open(path.string().c_str(), O_RDONLY); assert(fd != -1); struct stat stat; assert(fstat(fd, &stat) != -1); if (stat.st_size > 0) { char* const map = static_cast<char*>(mmap(0, stat.st_size, PROT_READ, MAP_SHARED, fd, 0)); assert(map != MAP_FAILED); assert(madvise(map, stat.st_size, MADV_SEQUENTIAL) != -1); grep_core(map, stat.st_size); assert(munmap(map, stat.st_size) != -1); } assert(close(fd) != -1); } void grep_core(const char* const map, size_t size) const { const char* const bof = map; const char* const eof = map + size; const char* bol = bof; while (bol < eof) { const char* eol = strchr(bol, '\n'); if (eol == 0) { eol = eof; } if (boost::regex_search(bol, eol, regex_)) { fwrite(bol, 1, eol - bol, stdout); fputc('\n', stdout); } bol = eol + 1; } } }; int main(int argc, char **argv) { assert(argc == 3); const string pattern(argv[1]); const fs::path directory(argv[2]); const grep grep(directory, pattern); grep.perform(); return 0; }
Debian GNU/Linux sarge では以下のように実行してコンパイルしました。
% sudo apt-get install libboost-filesystem-dev % sudo apt-get install libboost-regex-dev % g++ -o recgrep -Wall recgrep.cpp -pthread -lboost_filesystem -lboost_regex
実行速度は LANG=C で実行したときの GNU grep 2.5.1 の方が断然高速でした。最近の GNU grep は環境変数 LANG を ja_JP.utf8 などに設定すると何十倍も遅くなるようです。
% repeat 3 time LANG=C egrep -R foo /usr/share/emacs >/dev/null 0.15s total : 0.10s user 0.06s system 104% cpu 0.15s total : 0.06s user 0.09s system 100% cpu 0.14s total : 0.09s user 0.05s system 97% cpu % repeat 3 time ./recgrep foo /usr/share/emacs > /dev/null 2.44s total : 2.39s user 0.05s system 99% cpu 2.44s total : 2.35s user 0.09s system 99% cpu 2.46s total : 2.39s user 0.06s system 99% cpu % repeat 3 time LANG=ja_JP.utf8 egrep -R foo /usr/share/emacs >/dev/null 9.46s total : 9.34s user 0.11s system 99% cpu 9.60s total : 9.42s user 0.12s system 99% cpu 9.37s total : 9.23s user 0.14s system 100% cpu
ところで、上のコードの traverse の第2引数に対して boost::bind() を使ってメンバ関数 grep::grep_file をファンクタ化して渡しています。メンバ関数のポインタで渡す場合は以下のようになります。そもそも traverse は第2引数を取る必要はないのですが (直接 grep_file() を呼べばいい)、遊びでやっています。
traverse(directory_, &grep::grep_file); ... void traverse(const fs::path& directory, void (grep::*func)(const fs::path&)) const { ... (this->*func)(*it);
まとめ
今回は Boost の正規表現のクラスを使いました。Boost には他にも便利なものがたくさん入っていて C++ でプログラムを書くのに大変便利です。Boost を使うにあたっては Let's Boostが参考になります。