🙌
Goでひらがなをカタカナに変換する処理を書いてベンチマークを取ってみた
GoにはUnicodeの特定の文字に対して、code pointを一定数ずらすことができるようになっています。
この機能を使ってひらがなをカタカナにするコードを書いてみました。
package jptokenizer
import (
"strings"
"unicode"
)
var (
kanaConv = unicode.SpecialCase{
unicode.CaseRange{
Lo: 0x3040, // ぁ
Hi: 0x309F, // ん
Delta: [unicode.MaxCase]rune{
0x30A0 - 0x3040,
0,
0,
},
},
}
)
func ToKatakana(s string) string {
return strings.ToUpperSpecial(kanaConv, s)
}
結構簡単にかけますよね。
ただ、これはひらがなとカタカナをマッピングしたMapから変換するのと比べると速いのでしょうか?
で、こんなコードを書いてbenckmarkを取ってみました
package jptokenizer
import (
"strings"
"unicode"
)
var (
kanaConv = unicode.SpecialCase{
// Hiragana: https://www.unicode.org/charts/PDF/U3040.pdf
// Katakana: https://www.unicode.org/charts/PDF/U30A0.pdf
unicode.CaseRange{
Lo: 0x3040, // Lo: ぁ
Hi: 0x309F, // Hi: ん
Delta: [unicode.MaxCase]rune{
0x30A0 - 0x3040,
0,
0,
},
},
}
hiraganaToKatakanaMap = map[string]string{
"あ": "ア",
"い": "イ",
"う": "ウ",
"え": "エ",
"お": "オ",
"か": "カ",
"き": "キ",
"く": "ク",
"け": "ケ",
"こ": "コ",
"さ": "サ",
"し": "シ",
"す": "ス",
"せ": "セ",
"そ": "ソ",
"た": "タ",
"ち": "チ",
"つ": "ツ",
"て": "テ",
"と": "ト",
"な": "ナ",
"に": "ニ",
"ぬ": "ヌ",
"ね": "ネ",
"の": "ノ",
"は": "ハ",
"ひ": "ヒ",
"ふ": "フ",
"へ": "ヘ",
"ほ": "ホ",
"ま": "マ",
"み": "ミ",
"む": "ム",
"め": "メ",
"も": "モ",
"や": "ヤ",
"ゆ": "ユ",
"よ": "ヨ",
"ら": "ラ",
"り": "リ",
"る": "ル",
"れ": "レ",
"ろ": "ロ",
"わ": "ワ",
"を": "ヲ",
"ん": "ン",
}
)
func ToKatakana(s string) string {
return strings.ToUpperSpecial(kanaConv, s)
}
func ToKatakanaFromMap(s string) string {
var result strings.Builder
result.Grow(len(s))
for _, r := range s {
char := string(r)
if katakana, ok := hiraganaToKatakanaMap[char]; ok {
result.WriteString(katakana)
} else {
result.WriteString(char)
}
}
return result.String()
}
benchmarkコード
package jptokenizer
import "testing"
var (
// ベンチマーク用のテストデータ
shortText = "あいうえお"
mediumText = "これはテストです。ひらがなとカタカナの変換をテストします。"
longText = "これは非常に長いテストテキストです。ひらがなからカタカナへの変換のパフォーマンスをテストするために使用されます。たくさんのひらがなが含まれており、変換処理の性能を測定できます。実際のアプリケーションでは、このような長いテキストを処理することもあるでしょう。"
mixedText = "Hello世界!これはmixed textです。ひらがなとEnglishが混在しています。"
)
// ベンチマーク: ToKatakana関数のテスト
func BenchmarkToKatakana_Short(b *testing.B) {
for i := 0; i < b.N; i++ {
ToKatakana(shortText)
}
}
func BenchmarkToKatakana_Medium(b *testing.B) {
for i := 0; i < b.N; i++ {
ToKatakana(mediumText)
}
}
func BenchmarkToKatakana_Long(b *testing.B) {
for i := 0; i < b.N; i++ {
ToKatakana(longText)
}
}
func BenchmarkToKatakana_Mixed(b *testing.B) {
for i := 0; i < b.N; i++ {
ToKatakana(mixedText)
}
}
// ベンチマーク: ToKatakanaFromMap関数のテスト
func BenchmarkToKatakanaFromMap_Short(b *testing.B) {
for i := 0; i < b.N; i++ {
ToKatakanaFromMap(shortText)
}
}
func BenchmarkToKatakanaFromMap_Medium(b *testing.B) {
for i := 0; i < b.N; i++ {
ToKatakanaFromMap(mediumText)
}
}
func BenchmarkToKatakanaFromMap_Long(b *testing.B) {
for i := 0; i < b.N; i++ {
ToKatakanaFromMap(longText)
}
}
func BenchmarkToKatakanaFromMap_Mixed(b *testing.B) {
for i := 0; i < b.N; i++ {
ToKatakanaFromMap(mixedText)
}
}
結果
% go test -bench=. -benchmem
goos: darwin
goarch: arm64
pkg: github.com/SpringMT/jptokenizer
cpu: Apple M3 Pro
BenchmarkToKatakana_Short-12 19980058 58.33 ns/op 24 B/op 1 allocs/op
BenchmarkToKatakana_Medium-12 2502476 483.9 ns/op 96 B/op 1 allocs/op
BenchmarkToKatakana_Long-12 579843 2065 ns/op 416 B/op 1 allocs/op
BenchmarkToKatakana_Mixed-12 2528209 472.7 ns/op 96 B/op 1 allocs/op
BenchmarkToKatakanaFromMap_Short-12 15088131 80.50 ns/op 16 B/op 1 allocs/op
BenchmarkToKatakanaFromMap_Medium-12 2321415 521.3 ns/op 96 B/op 1 allocs/op
BenchmarkToKatakanaFromMap_Long-12 456230 2603 ns/op 384 B/op 1 allocs/op
BenchmarkToKatakanaFromMap_Mixed-12 1620055 716.9 ns/op 96 B/op 1 allocs/op
ToKatakana vs ToKatakanaFromMap 比較:
- 短いテキスト:
ToKatakana: 58.33 ns/op vs ToKatakanaFromMap: 80.50 ns/op
差: ToKatakanaが約1.4倍高速 - 中程度のテキスト:
ToKatakana: 483.9 ns/op vs ToKatakanaFromMap: 521.3 ns/op
差: ToKatakanaが約1.1倍高速 - 長いテキスト:
ToKatakana: 2065 ns/op vs ToKatakanaFromMap: 2603 ns/op
差: ToKatakanaが約1.3倍高速
ToKatakanaのほうが若干ではありますが速いですね。
これだとコードが短くかけるToUpperSpecialのほうを使ったほうがよさそうですね。
Discussion