Mecabはバインディングで3.1倍速くなる

はじめに

こないだIO.popenによるMecabの呼び出しを書きましたが,バインディングを利用した方法を書きます.
こっちの方が速いのでおすすめ.

バインディングのインストール

sudo port install rb-mecab +utf8only

MacPortsではたったこれだけ.
文字コードにはUTF-8を使うので+utf8onlyを指定しましょう.

実行例

サンプルとして,Rubyのホームページに載ってるテキストを100回Mecabを通してみます.

require 'Benchmark'

text = <<EOT
Rubyは、手軽なオブジェクト指向プログラミングを実現するための種々の機能を持つオブジェクト指向スクリプト言語です。本格的なオブジェクト指向言語であるSmalltalk、EiffelやC++などでは大げさに思われるような領域でのオブジェクト指向プログラミングを支援することを目的としています。もちろん通常の手続き型のプログラミングも可能です。
Rubyはテキスト処理関係の能力などに優れ、Perlと同じくらい強力です。さらにシンプルな文法と、例外処理やイテレータなどの機構によって、より分かりやすいプログラミングが出来ます。
まあ、簡単にいえばPerlのような手軽さで「楽しく」オブジェクト指向しようという言語です。どうぞ使ってみてください。
EOT

puts Benchmark::measure { 
  100.times { 
    counter = My::WordCounter.new
    counter.count2(text)
  }
}

puts Benchmark::measure { 
  100.times { 
    counter = My::WordCounter.new
    counter.count(text)
  }
}

結果はこんな感じ.

  0.670000   0.060000   0.730000 (  0.732847) # => MeCab::Tagger
  0.690000   0.260000   2.300000 (  2.258337) # => IO.popen

ソース

require 'MeCab'

module My
  class WordCounter
    MECAB = '/usr/local/bin/mecab'
    NECESSARY_FEATURE = %w/名詞,固有名詞 名詞,一般 名詞,副詞可能 名詞,サ変接続 名詞,形容動詞語幹 動詞.*五段/
    UNNECESSARY_FEATURE = %w/名詞,数 名詞,接尾 名詞,非自立 接頭詞,名詞接続/

    def initialize
      @mecab = MeCab::Tagger.new
      @result = Hash.new(0)
    end

    attr_reader :result

    # mainループ
    def count(text)
      words = split_to_words(text)
      count_up_result(words)
      self
    end

    def count2(text)
      words = split_to_words2(text)
      count_up_result(words)
      self
    end

    # Mecabで単語に分割し,必要な品詞のみ抽出する
    def split_to_words(text)
      results = IO.popen(MECAB, 'r+') { |mecab|
        mecab.puts(text)
        mecab.close_write
        mecab.readlines
      }
      results.map { |result| necessary?(result) ? result.split.first : nil }.compact
    end

    # 単語は全て小文字に変換する
    def split_to_words2(text)
      result = []
      node = @mecab.parseToNode(text)
      while node
        result << node.surface.downcase if necessary?(node.feature)
        node = node.next
      end
      result
    end

    # 単語として使うする属性を判断する
    def necessary?(feature)
      case feature
      when Regexp.new(NECESSARY_FEATURE.join('|'))
        true
      when Regexp.new(UNNECESSARY_FEATURE.join('|'))
        false
      when /名詞/
        warn feature
        false
      else
        false
      end
    end

    # 単語数をカウント
    def count_up_result(words)
      words.each do |word|
        @result[word] += 1
      end
    end
  end
end

最後に

インストールも簡単なので,Mecabはぜひバインディング経由で使いましょう.