Python で分かち書き の速度比較 / janome, sudachi, ginza, mecab, fugashi, tinysegmenter

# Python で形態素解析 の速度比較
- シンプルに分かち書きだけを期待して
- pip でサクッとインストール終わるヤツ
- 環境 `docker pull python:3-slim`

## 比較対象
- janome
- sudachi
- ginza
- mecab
- fugashi
- tinysegmenter

## 準備
- `pip install janome`
- `pip install sudachipy`
- `pip install ginza sudachidict_core`
- `pip install mecab-python3`
- `pip install fugashi[unidic-lite]`
- `pip install tinysegmenter`

<details>
<summary>参考：pip freeze</summary><div>

```ini:reqirements.txt
blis==0.4.1
catalogue==1.0.0
certifi==2020.4.5.2
chardet==3.0.4
cymem==2.0.3
Cython==0.29.19
dartsclone==0.9.0
fugashi==0.2.3
ginza==3.1.2
idna==2.9
ja-ginza==3.1.0
ja-ginza-dict==3.1.0
Janome==0.3.10
mecab-python3==0.996.5
murmurhash==1.0.2
numpy==1.18.5
plac==1.1.3
preshed==3.0.2
requests==2.23.0
sortedcontainers==2.1.0
spacy==2.2.4
srsly==1.0.2
SudachiDict-core==20200330
SudachiPy==0.4.5
thinc==7.4.0
tinysegmenter==0.4
tqdm==4.46.1
unidic-lite==1.0.6
urllib3==1.25.9
wasabi==0.6.0
```
</div></details>

## コードと実行結果

<details>
<summary>コードを見る</summary><div>

```python:ma.py
import time

from janome.tokenizer import Tokenizer
from sudachipy import dictionary
import spacy
from MeCab import Tagger as mecab
from fugashi import Tagger as fugashi
import tinysegmenter

def use_janome(s, cache=None):
  t = Tokenizer() if cache is None else cache
  [token.surface for token in t.tokenize(s)]
  return t


def use_sudachi(s, cache=None):
  t = dictionary.Dictionary().create() if cache is None else cache
  [token.surface() for token in t.tokenize(s)]
  return t


def use_ginza(s, cache=None):
  t = spacy.load('ja_ginza') if cache is None else cache
  [token for token in t(s)]
  return t


def use_mecab(s, cache=None):
  t = mecab('-Owakati')
  [token for token in t.parse(s).split(' ')]
  return t


def use_fugashi(s, cache=None):
  t = fugashi('-Owakati')
  t(s)
  return t


def use_tinysegmenter(s, cache=None):
  t = tinysegmenter.TinySegmenter()
  t.tokenize(s)
  return t


def stopwatch(func, times=100):
  # 桃太郎 芥川龍之介 https://www.aozora.gr.jp/cards/000879/card100.html
  s = 'むかし、むかし、大むかし、ある深い山の奥に大きい桃《もも》の木が一本あった。大きいとだけではいい足りないかも知れない。この桃の枝は雲の上にひろがり、この桃の根は大地《だいち》の底の黄泉《よみ》の国にさえ及んでいた。何でも天地｜開闢《かいびゃく》の頃《ころ》おい、伊弉諾《いざなぎ》の尊《みこと》は黄最津平阪《よもつひらさか》に八《やっ》つの雷《いかずち》を却《しりぞ》けるため、桃の実《み》を礫《つぶて》に打ったという、――その神代《かみよ》の桃の実はこの木の枝になっていたのである。'
  time_s = time.perf_counter()
  cache = None
  for i in range(times):
    cache = func(s, cache)
  time_e = time.perf_counter()
  return time_e - time_s


def main():
  d = {
    'janome': stopwatch(use_janome), 
    'sudachi': stopwatch(use_sudachi), 
    'ginza': stopwatch(use_ginza), 
    'mecab': stopwatch(use_mecab), 
    'fugashi': stopwatch(use_fugashi), 
    'tinysegmenter': stopwatch(use_tinysegmenter)
    }
  d = sorted(d.items(), key=lambda x:x[1])
  print('\n'.join(map(str, d)))


if __name__ == '__main__':
  main()

```
</div></details>

```shell:結果
('mecab', 0.09558620000007068)
('fugashi', 0.1353556000003664)
('tinysegmenter', 0.655696199999511)
('janome', 3.070499899999959)
('sudachi', 5.18910169999981)
('ginza', 9.577376000000186)
```

## 感想
- そら MeCab は Python実装でなくネイティブなんで **圧倒的** 、知ってた
- 便利とか精度とかに応じて重くなるだろうと思ってた
- tinysegmenter は目的が分かち書き特化なので Python実装だけど早い
- janome の速さは期待通り、sudachi, ginza と差があるもんなんですね
- 初めて速度比較した
- はじめ tokenizer 初期化をループ中にいれちゃってひどかった
- nagisa, UniDic2UD はインストールしんどいので今回不採用
- (コメントありがとうございます) Windows を主戦場にしてたら MeCab ってインストールが面倒だと思い込んでた

以上