はじめに
言語処理100本ノック 2015 の 03. 円周率 の問題 を
- Golang で解いて
- テストを書いてベンチマークをとってみた
という投稿になります。
Golang で解いてみた
package main
import (
"fmt"
"regexp"
"strings"
_ "unicode/utf8"
)
var sentence string = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
func main() {
// 解答例その1
fmt.Println(answer1())
// 解答例その2
fmt.Println(answer2())
}
func answer1() []int {
// 単語に分解
words := strings.Split(sentence, " ")
var pi []int
for _, word := range words {
word = strings.Trim(word, ",")
word = strings.Trim(word, ".")
// pi = append(pi, utf8.RuneCountInString(word))
pi = append(pi, len(word))
}
return pi
}
func answer2() []int {
// 文を正規表現で単語に分解
words := regexp.MustCompile(`\s|"|,|\.`).Split(sentence, -1)
var pi []int
for _, word := range words {
// 文末がスプリットされた場合、words の配列の最後に空文字が入る
if word != "" {
// pi = append(pi, utf8.RuneCountInString(word))
pi = append(pi, len(word))
}
}
return pi
}
answer1() は、最初に自分で考えて書いた解き方で、
answer2() 先輩にコードレビューでご指摘を頂いて書きなおした解き方です。
この 2 つの解き方の違いは、(コードを見たらわかりますが)文を単語に分解する箇所です。
answer1() が、単語をまず半角スペースで split してから、
その後にカンマとピリオドを trim して単語を抽出しているのに対して、
answer2() では、正規表現を用いることによって、answer1() と同じことを実現しています。
テストを書いてベンチマークをとってみた
ベンチマークを取ってみようと思った理由は、
どっちの書き方の方がいいかな?と考えた時に、
answer2() の方が、trim の処理がない分スマートだと思ったのですが、
なんとなく正規表現といえば処理が重そう・・・というイメージがあったので、
answer1() と、answer2() とで、
処理速度にどれくらい差が出るのかな?と疑問に思ったからです。
package main
import (
"reflect"
"testing"
)
var expect = []int{3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9}
func TestAnswer1(t *testing.T) {
answer1 := answer1()
if !reflect.DeepEqual(answer1, expect) {
t.Fatalf("%v==%v", answer1, expect)
}
}
func TestAnswer2(t *testing.T) {
answer2 := answer2()
if !reflect.DeepEqual(answer2, expect) {
t.Fatalf("%v==%v", answer2, expect)
}
}
func BenchmarkAnswer1(b *testing.B) {
for i := 0; i < b.N; i++ {
answer1()
}
}
func BenchmarkAnswer2(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
answer2()
}
}
以下、ベンチマークの結果です。
// 文字数カウントに utf8.RuneCountInString() を使用
# go test -bench .
[ `go test -bench .` | done: 6.1636163s ]
PASS
BenchmarkAnswer1 100000 20692 ns/op
BenchmarkAnswer2 20000 75057 ns/op
ok _/C_/Users/mypath/study/100-knock 4.900s
何度か計測してみましたが、
answer1() と answer2() の処理時間の差でいうと、だいたい 3 倍差に落ち着くという感じでした。
ただ、差が 3 倍といっても、ナノ秒の世界なので、
ミリ秒に変換すると、約 0.02 ミリ秒と約 0.08ミリ秒・・・となり、誤差の範囲とも言えそうです。
あと、ちなみに、
最初は文字数のカウントに utf8.RuneCountInString() を使っていましたが、
今回の問題は英語なので len() を使ったほうが速くなりそうだということで、
こちらもベンチマークを取得してみました。
// 文字数カウントに len() を使用
# go test -bench .
[ `go test -bench .` | done: 6.6896689s ]
PASS
BenchmarkAnswer1 100000 19341 ns/op
BenchmarkAnswer2 20000 59505 ns/op
ok _/C_/Users/mypath/study/100-knock 5.419s
ちょっと速くなった模様。
結論
-
処理速度に差が出るのかな?
→ 差はある。比較という意味では約 3 倍差。
でもミリ秒にも満たないナノ秒の世界。
作るシステムにもよるけど、一般的には誤差の範囲といってよさそう。 -
どっちの書き方の方がいいかな?
→ 英文の単語抽出は、正規表現を使ったほうが簡潔でよさそう。
参考URL
以上