go で、html fillinform をしたかったので、@gfx さんの HTML::FillInForm::Lite を go に移植しました。こちら→ fillinform
同様の機能をもつパッケージには、htmlfiller がありました。こちらは regexp ではなく exp-html をつかっています。
まずは愚直に regexp の実装をそのまま移植してみたんですが、なにしろ go の regexp が遅かったので、調べてみると go の regexp が遅いのは 天地開闢から周知の事実でした。
今もあまり変わってないようです。
ということで、このままだと perl の高速な regexp には到底太刀打ち出来ないので regexp を使わないようにできそうなところとか、そもそも自分の残念な go 実装を書き換えました。
unquote を bytes.Trim に
func (f Filler) unquote(tag []byte) []byte {
newTag := f.compileMultiLine(`['"](.*)['"]`).FindSubmatch(tag)
if len(newTag) == 2 {
return newTag[1]
}
return tag
}
↓
func (f Filler) unquote(tag []byte) []byte {
return bytes.Trim(tag, `'"`)
}
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() でコンパイルして保持するように変えた。
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 に
func (f Filler) escapeHTML(tag string) string {
tag = regexp.MustCompile(`&`).ReplaceAllString(tag, `&`)
tag = regexp.MustCompile(`<`).ReplaceAllString(tag, `<`)
tag = regexp.MustCompile(`>`).ReplaceAllString(tag, `>`)
tag = regexp.MustCompile(`"`).ReplaceAllString(tag, `"`)
return tag
}
↓
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)
}
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回回した結果)
$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 をそのまま渡して動くようにしたかったため、渡ってこないパラメータに関しては空が指定されたかのように動きます。このへんは何が正しいのかよくわからない。ですね。
書いてる過程で知ったこと
リテラル
formData := map[string][]string{
"gender": []string{"1"},
}
は
formData := map[string][]string{
"gender": {"1"},
}
でいい。これは、1.5 からですかね。
3つ以上のスライスを append する
a := []byte("X")
b := []byte("Y")
c := []byte("Z")
d := append(a, append(b, c...)...)
もっとスマーフな方法なかろうか?
って思ってこういうの書いてみたものの
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
}
2回ループするとか微妙なので、結局普通に append ネストしました。
文字列リテラル中に変数を展開する方法
ない
プラス + で繋いで作るしか無い...
perl の /.../msi は
(?msi: ... )
ちなみに //x に相当するものはありません。
pprof は mac os x では動かない
動きません。カーネルにパッチあてればいいそうですが、やりませんでした。