【Java】正規表現で制御文字を除去したらめっぽう遅かった

INDEX
-----
- 概要
- 検証コード
- 検証結果
- まとめ
- 追記（2018/06/05）
- 追記のまとめ（2018/06/05）

概要
-----
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">性器でも無いし痴漢でもないのです！<a href="https://twitter.com/hashtag/%E7%A4%BE%E7%95%9C%E3%81%A1%E3%82%83%E3%82%93%E5%8F%B0%E8%A9%9E%E3%83%A1%E3%83%BC%E3%82%AB%E3%83%BC?src=hash&amp;ref_src=twsrc%5Etfw">#社畜ちゃん台詞メーカー</a> <a href="https://t.co/rHdsYHelTL">https://t.co/rHdsYHelTL</a> <a href="https://t.co/jo7oZxH3f9">pic.twitter.com/jo7oZxH3f9</a></p>&mdash; まあにい (@mahny_b) <a href="https://twitter.com/mahny_b/status/1003318090870865920?ref_src=twsrc%5Etfw">2018年6月3日</a></blockquote>

てなことで、String#replaceAllで制御文字を除去したらサーバ応答が結構遅くなったので、検証コードを書いてみたら確かに遅そうだと分かったよと言う話です。

検証コード
----------
- 次の様な処理をするコードを準備した（[GitHubへ](https://github.com/mahny-b/replaceAll-charAt)）
    - ASCIIのみのテストデータの生成
    - String#replaceAllで制御文字を置換する
        - 1000回実施した時の時間を記録する
    - String#chatAtで制御文字を判別して新たに文字列を生成
        - 1000回実施した時の時間を記録する

## コードの使い方
- [GenerateTestDataクラス](https://github.com/mahny-b/replaceAll-charAt/blob/master/src/jp/gr/mahny_conf/replaceall_charat/GenerateTestData.java)を実行して、テストデータを生成する。
    - resources/test.txt が出力される
    - ![image.png](https://qiita-image-store.s3.amazonaws.com/0/92447/1c25c6fb-fc96-55d9-1dbc-7d1648ffa911.png)
- [SpeedCrazyReplaceAllクラス](https://github.com/mahny-b/replaceAll-charAt/blob/master/src/jp/gr/mahny_conf/replaceall_charat/SpeedCrazyReplaceAll.java)と[SpeedCrazyCharAtクラス](https://github.com/mahny-b/replaceAll-charAt/blob/master/src/jp/gr/mahny_conf/replaceall_charat/SpeedCrazyCharAt.java)を実行する

検証結果
----------
- 私のPC環境では大体replaceAllよりもcharAtの方が大体2倍速いという事になった。

|試行回数| replaceAll | charAt |
|-------|------------|--------|
| 1     | 11,170     | 5,377  |
| 2     | 11,337     | 5,437  |
| 3     | 11,203     | 5,418  |
| 4     | 11,114     | 5,489  |
| 5     | 11,294     | 5,527  |
※単位はms

まとめ
----------
- 正規表現は便利なんだけど、大量データを捌かないといけない場合等は、全部置換する実装ではなく、限定的に使うようにするなどの注意が必要だなーと思いました。[^1]

追記（2018/06/05）
--------------------
- Pattern.compileで予めインスタンスを作ったらどうかというコメントがありましたので、追加検証しました。

## 検証コード
- [SpeedCrazyPatternクラス](https://github.com/mahny-b/replaceAll-charAt/blob/master/src/jp/gr/mahny_conf/replaceall_charat/SpeedCrazyPattern.java)を追加。

## 検証結果
- ↑の検証では、横着してEclipse上から実施していたので今度はコンソール上で実施しました。

```
mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.GenerateTestData

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.SpeedCrazyReplaceAll
合計時間 : 12006(ms), 平均時間 : 12(ms)

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.SpeedCrazyPattern
合計時間 : 11023(ms), 平均時間 : 11(ms)

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.SpeedCrazyCharAt
合計時間 : 5589(ms), 平均時間 : 5(ms)

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.GenerateTestData

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.SpeedCrazyReplaceAll
合計時間 : 11189(ms), 平均時間 : 11(ms)

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.SpeedCrazyPattern
合計時間 : 10947(ms), 平均時間 : 10(ms)

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.SpeedCrazyCharAt
合計時間 : 5573(ms), 平均時間 : 5(ms)

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.GenerateTestData

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.SpeedCrazyReplaceAll
合計時間 : 11038(ms), 平均時間 : 11(ms)

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.SpeedCrazyPattern
合計時間 : 11152(ms), 平均時間 : 11(ms)

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.SpeedCrazyCharAt
合計時間 : 5594(ms), 平均時間 : 5(ms)

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.GenerateTestData

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.SpeedCrazyReplaceAll
合計時間 : 11201(ms), 平均時間 : 11(ms)

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.SpeedCrazyPattern
合計時間 : 10829(ms), 平均時間 : 10(ms)

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.SpeedCrazyCharAt
合計時間 : 5593(ms), 平均時間 : 5(ms)

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.GenerateTestData

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.SpeedCrazyReplaceAll
合計時間 : 11164(ms), 平均時間 : 11(ms)

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.SpeedCrazyPattern
合計時間 : 11049(ms), 平均時間 : 11(ms)

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
> java -cp .;speedcrazy.jar jp.gr.mahny_conf.replaceall_charat.SpeedCrazyCharAt
合計時間 : 5584(ms), 平均時間 : 5(ms)

mahny@ALFA c:\Users\mahny\Desktop\speedcrazy
>
```
|試行回数| String#replaceAll | Pattern#match | String#charAt |
|--------|-------------------|---------------|---------------|
| 1      | 12,006            | 11,023        | 5,589         |
| 2      | 11,189            | 10,497        | 5,573         |
| 3      | 11,038            | 11,152        | 5,594         |
| 4      | 11,201            | 10,829        | 5,593         |
| 5      | 11,164            | 11,049        | 5,584         |
※単位はms

うーむ、若干速くなっている…けど、思ったほど効果ないな…。コードの書き方が悪いのかな？


追記のまとめ（2018/06/05）
-------------------------
- 単一のPatternインスタンス[^2]を準備した場合で、String#replaceAllを使った時より約4%ほど改善
- とはいえ、charAtを使える場面ではその方がいいし、正規表現の使いどころは気をつけようという考えは変わらず



[^1]: 実際に正規表現を使った実装をした時、検証APが処理しきれず無応答になってしまったので…コワイコワイ
[^2]: Patternクラスのコードを見る限りPattern.compile(String)の引数によってはインスタンス生成に時間が掛かりそうな感じだったので、今回の様な単純な制御文字削除だけでなく複雑な正規表現を持ってきたらもっと差が付いたんじゃないかと思った。そこまで複雑ならcharAtなんて使えた物じゃないだろうし今度検証してみようかな。