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

go で、html fillinform をしたかったので、@gfx さんの HTML::FillInForm::Lite を go に移植しました。こちら→ fillinform

同様の機能をもつパッケージには、htmlfiller がありました。こちらは regexp ではなく exp-html をつかっています。

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

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

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

ということで、このままだと 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, `&lt;`)
    tag = regexp.MustCompile(`>`).ReplaceAllString(tag, `&gt;`)
    tag = regexp.MustCompile(`"`).ReplaceAllString(tag, `&quot;`)
    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 では動かない

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

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

その他

@lestrrat さんの go-pcre2 が git に上がってたので、機会をみて置き換えてみたいです。


『 Go 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。