Compressed Suffix ArrayとFM-Indexの性能を比較してみた

せっかくFM-Indexを実装したので、以前作ったCompressed Suffix Arrayとの性能比較をしてみた。
私の実装の比較なので本来の手法の良し悪しとは必ずしも一致しない可能性がある。あくまで参考程度に。


データにはいつもの住所データ(http://jusyo.jp/)を用いた。

$$ mkdir var
$$ cd var
$$ wget http://jusyo.jp/downloads/new/csv/csv_zenkoku.zip
$$ unzip csv_zenkoku.zip

$$ nkf -w zenkoku.txt > zenkoku.txt.utf8
$$ perl -ne '@a=split(/\t/);$s="$a[7]$a[9]$a[11]";
     $s=~s/"//g;print "$s\n"' zenkoku.txt.utf8 > zenkoku.key
$$ sort -u zenkoku.key -o zenkoku.key
$$ cd ..


とりあえず比較したのはデータサイズと、get_rows(検索キーに何件ヒットしたかを返す)という操作。以下の操作はshellinford/で行った。
まずデータをつくる。

$$ tsubomi_mkary var/zenkoku.key
$$ tsubomi_mkcsa var/zenkoku.key
$$ sample/make_fm_index var/zenkoku.key.fm < var/zenkoku.key

するとデータサイズは以下のようになった。

元テキスト: 3,872,813 byte
Suffix Array: 19,364,065 byte
Compressed Suffix Array: 6,093,288 byte
FM-Index: 5, 709, 560 byte

そしてget_rowsの比較には以下のコードを用いた。

#include "shellinford_fm_index.h"
#include "tsubomi_basic_searcher.h"
#include "tsubomi_compressor.h"
#include <iostream>
#include <ctime>

using namespace std;
int main(int argc, char **argv) {
  try {
    vector<string> v;
    string s;
    while (cin >> s) { v.push_back(s); }
    vector<string>::iterator i;
    clock_t tmp;
    double  sec;

    // tsubomi::basic_searcher
    tsubomi::basic_searcher bs(argv[1]);
    tmp = clock();
    for (i = v.begin(); i != v.end(); i++) {
      bs.search(i->c_str());
    }
    sec = double(clock() - tmp) / CLOCKS_PER_SEC;
    cout << sec << "s" << endl;

    // tsubomi::compressor
    tsubomi::compressor c(argv[1]);
    tmp = clock();
    for (i = v.begin(); i != v.end(); i++) {
      c.search(i->c_str());
    }
    sec = double(clock() - tmp) / CLOCKS_PER_SEC;
    cout << sec << "s" << endl;

    // shellinford::fm_index
    string fn = argv[1];
    fn += ".fm";
    shellinford::fm_index fm;
    fm.read(fn.c_str());
    tmp = clock();
    for (i = v.begin(); i != v.end(); i++) {
      fm.get_rows(*i);
    }
    sec = double(clock() - tmp) / CLOCKS_PER_SEC;
    cout << sec << "s" << endl;

  } catch (const char *err) {
    cerr << "ERROR: " << err << endl;
  }
  return 0;
}

これをeval.ccとして保存する。元データをシャッフルしたデータを入力として評価すると以下のようになった。

$$ shuf var/zenkoku.key > var/zenkoku.shuf
$$ g++ -O2 eval.cc -ltsubomi -lshellinford
$$ ./a.out var/zenkoku.key < var/zenkoku.shuf
0.128185s  # Suffix Array
8.26793s  # Compressed Suffix Array
4.56001s  # FM-Index

というわけで一応、(私の実装では)FM-IndexがCompressed Suffix Arrayよりもデータサイズ、get_rowsどちらも良い結果になった。