LoginSignup
9
9

More than 5 years have passed since last update.

Golang で言語処理100本ノックの No.3 の問題を解いてベンチマークとってみた

Last updated at Posted at 2015-04-16

はじめに

言語処理100本ノック 201503. 円周率 の問題

  • Golang で解いて
  • テストを書いてベンチマークをとってみた

という投稿になります。

Golang で解いてみた

03.go
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() とで、
処理速度にどれくらい差が出るのかな?と疑問に思ったからです。

03_test.go
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

以上

9
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9