go の regexp が遅いと知らずに regexp で form に fillinするモジュールを書いた

go で、html fillinform をしたかったので、@gfx さんの [HTML::FillInForm::Lite](https://github.com/gfx/p5-HTML-FillInForm-Lite) を go に移植しました。こちら→ [fillinform](https://github.com/sheercat/fillinform)

同様の機能をもつパッケージには、[htmlfiller](https://github.com/griffy/htmlfiller) がありました。こちらは regexp ではなく [exp-html](https://github.com/levigross/exp-html) をつかっています。

まずは愚直に regexp の実装をそのまま移植してみたんですが、なにしろ go の regexp が遅かったので、調べてみると go の regexp が遅いのは 天地開闢から周知の事実でした。

http://qiita.com/naoina/items/d71ddfab31f4b29f6693

今もあまり変わってないようです。

ということで、このままだと perl の高速な regexp には到底太刀打ち出来ないので regexp を使わないようにできそうなところとか、そもそも自分の残念な go 実装を書き換えました。

## unquote を bytes.Trim に

```go
func (f Filler) unquote(tag []byte) []byte {
    newTag := f.compileMultiLine(`['"](.*)['"]`).FindSubmatch(tag)
    if len(newTag) == 2 {
        return newTag[1]
    }
    return tag
}
```
↓

```go
func (f Filler) unquote(tag []byte) []byte {
    return bytes.Trim(tag, `'"`)
}
```

```sh
BenchmarkUnquote-4     	  200000	     11279 ns/op	   41681 B/op	      36 allocs/op
BenchmarkUnquote-4     	20000000	       104 ns/op	      32 B/op	       1 allocs/op
```

100倍 ／(^o^)＼

## メモリアロケーションがおおいのは、毎回 regexp.MustCompile していたからなので、init() でコンパイルして保持するように変えた。

```sh
PASS
BenchmarkUnquote-4     	20000000	       106 ns/op	      32 B/op	       1 allocs/op
BenchmarkGetType-4     	  100000	     18935 ns/op	   46944 B/op	      75 allocs/op
BenchmarkGetValue-4    	  100000	     19344 ns/op	   47216 B/op	      78 allocs/op
BenchmarkGetName-4     	  100000	     20656 ns/op	   46944 B/op	      75 allocs/op
BenchmarkEscapeHTML-4  	   50000	     33839 ns/op	  162289 B/op	     118 allocs/op
BenchmarkFillInput-4   	   10000	    106380 ns/op	  235842 B/op	     378 allocs/op
BenchmarkFillTextarea-4	   20000	     90014 ns/op	  264868 B/op	     321 allocs/op
BenchmarkFillSelect-4  	   10000	    223276 ns/op	  448898 B/op	     874 allocs/op
BenchmarkFillOption-4  	   20000	     84422 ns/op	  193856 B/op	     342 allocs/op
BenchmarkFillinForm-4  	    2000	    966790 ns/op	 1996791 B/op	    3283 allocs/op
ok  	github.com/sheercat/fillinform	21.391s

PASS
BenchmarkUnquote-4     	20000000	       105 ns/op	      32 B/op	       1 allocs/op
BenchmarkGetType-4     	 1000000	      1256 ns/op	     112 B/op	       3 allocs/op
BenchmarkGetValue-4    	 1000000	      1968 ns/op	     112 B/op	       3 allocs/op
BenchmarkGetName-4     	  500000	      2673 ns/op	     112 B/op	       3 allocs/op
BenchmarkEscapeHTML-4  	  300000	      4455 ns/op	     880 B/op	      26 allocs/op
BenchmarkFillInput-4   	  100000	     16124 ns/op	     624 B/op	      15 allocs/op
BenchmarkFillTextarea-4	  200000	      7069 ns/op	     624 B/op	      14 allocs/op
BenchmarkFillSelect-4  	  100000	     20599 ns/op	    1040 B/op	      24 allocs/op
BenchmarkFillOption-4  	  300000	      5456 ns/op	     352 B/op	      10 allocs/op
BenchmarkFillinForm-4  	   10000	    128107 ns/op	    9136 B/op	     113 allocs/op
ok  	github.com/sheercat/fillinform	16.768s
```

当然ですが、メモリアロケーションが減って速くなった。／(^o^)＼

## regexp.MustCompile を減らすついでに escape html を bytes.Replace に

```go
func (f Filler) escapeHTML(tag string) string {
	tag = regexp.MustCompile(`&`).ReplaceAllString(tag, `&amp;`)
	tag = regexp.MustCompile(`<`).ReplaceAllString(tag, `&lt;`)
	tag = regexp.MustCompile(`>`).ReplaceAllString(tag, `&gt;`)
	tag = regexp.MustCompile(`"`).ReplaceAllString(tag, `&quot;`)
	return tag
}
```

↓

```go
func (f Filler) escapeHTML(tag []byte) []byte {
	return bytes.Replace(bytes.Replace(bytes.Replace(bytes.Replace(tag, []byte{'&'}, BAAmp, -1), []byte{'<'}, BALt, -1), []byte{'>'}, BAGt, -1), []byte{'"'}, BAQuot, -1)
}
```

```sh
BenchmarkEscapeHTML-4  	  300000	      4455 ns/op	     880 B/op	      26 allocs/op
BenchmarkEscapeHTML-4  	 1000000	      1004 ns/op	     336 B/op	       4 allocs/op
```

たいそう読みづらいけど、速くなった。／(^o^)＼

## 結果 (10000回回した結果)
```sh
$carton exec perl bench.pl
14.766043 at bench.pl line 1079.
$go run bench.go
37.802735974s
```

2.6倍遅いってとこまでこれたー。ワーイ／(^o^)＼

パッケージの仕様としてはまだ全然機能が足りない気もしますし、utf-8なのに bytes.Replace, bytes.Trim で処理していいんだっけ、とかそもそもフィルインする仕様が HTML::FillInForm::Lite とはちょっと違っています。
http.Request.PostForm をそのまま渡して動くようにしたかったため、渡ってこないパラメータに関しては空が指定されたかのように動きます。このへんは何が正しいのかよくわからない。ですね。

## 書いてる過程で知ったこと
### リテラル
```go
formData := map[string][]string{
	"gender": []string{"1"},
}
```
は

```go
formData := map[string][]string{
	"gender": {"1"},
}
```
でいい。これは、1.5 からですかね。

### 3つ以上のスライスを append する
```go
a := []byte("X")
b := []byte("Y")
c := []byte("Z")
d := append(a, append(b, c...)...)
```
もっとスマーフな方法なかろうか？

って思ってこういうの書いてみたものの

```go
func appendMultipleByteSlices(bSlices ...[]byte) []byte {
    i := 0
    for _, b := range bSlices {
        i = i + len(b)
    }
    capped := make([]byte, 0, i)
    for _, b := range bSlices {
        capped = append(capped, b...)
    }

    return capped
}
```
２回ループするとか微妙なので、結局普通に append ネストしました。

### 文字列リテラル中に変数を展開する方法
ない
プラス + で繋いで作るしか無い...

###  perl の /.../msi は
(?msi: ... )

ちなみに //x に相当するものはありません。

### pprof は mac os x では動かない

動きません。カーネルにパッチあてればいいそうですが、やりませんでした。

https://github.com/golang/go/issues/6047

## その他
@lestrrat さんの [go-pcre2](https://github.com/lestrrat/go-pcre2) が git に上がってたので、機会をみて置き換えてみたいです。