strings.ToLower() vs bytes.ToLower()
Golang의 ToLower(). 뭐가 더 빠를까?
결론
strings.ToLower를 사용하는 것보다 string을 []byte로 변환하여 bytes.ToLower 사용하는 것이 훨씬 빠르다.
package test
import (
"bytes"
"strings"
"testing"
)
const TestString = "Lower Testing StaRtINg. Files Containing teSts shoUld bE CalLeD."
func BenchmarkStringLower(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = strings.ToLower(TestString)
}
}
func BenchmarkByteLower(b *testing.B) {
for i := 0; i < b.N; i++ {
byt := []byte(TestString)
_ = string(bytes.ToLower(byt))
}
}
BenchmarkStringLower-10 1000000 211.0 ns/op
BenchmarkByteLower-10 1000000 113.1 ns/op
Why ?
사실 strings.ToLower 내부적으로 결국 []byte 를 쓴다.
// strings.ToLower
func ToLower(s string) string {
isASCII, hasUpper := true, false
for i := 0; i < len(s); i++ {
c := s[i]
if c >= utf8.RuneSelf {
isASCII = false
break
}
hasUpper = hasUpper || ('A' <= c && c <= 'Z')
}
if isASCII { // optimize for ASCII-only strings.
if !hasUpper {
return s
}
var b Builder
b.Grow(len(s))
for i := 0; i < len(s); i++ {
c := s[i]
if 'A' <= c && c <= 'Z' {
c += 'a' - 'A'
}
b.WriteByte(c)
}
return b.String()
}
return Map(unicode.ToLower, s)
}
위 코드의 Builder를 살펴보면…
type Builder struct {
addr *Builder // of receiver, to detect copies by value
buf []byte
}
func (b *Builder) grow(n int) {
buf := make([]byte, len(b.buf), 2*cap(b.buf)+n)
copy(buf, b.buf)
b.buf = buf
}
func (b *Builder) WriteByte(c byte) error {
b.copyCheck()
b.buf = append(b.buf, c)
return nil
}
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
이렇게 내부적으로 Builder를 생성하고 메모리 할당, len(string)만큼의 바이트 추가, 다시 string으로 변환 과정을 거치기 때문에 차라리 bytes.ToLower를 사용하는 것이 빠르다.
bytes.ToLower
// bytes.ToLower
func ToLower(s []byte) []byte {
isASCII, hasUpper := true, false
for i := 0; i < len(s); i++ {
c := s[i]
if c >= utf8.RuneSelf {
isASCII = false
break
}
hasUpper = hasUpper || ('A' <= c && c <= 'Z')
}
if isASCII { // optimize for ASCII-only byte slices.
if !hasUpper {
return append([]byte(""), s...)
}
b := make([]byte, len(s))
for i := 0; i < len(s); i++ {
c := s[i]
if 'A' <= c && c <= 'Z' {
c += 'a' - 'A'
}
b[i] = c
}
return b
}
return Map(unicode.ToLower, s)
}
속도가 중요하고, 길이가 긴 string에 대한 처리는 bytes를 사용하는 것이 좋겠다. 아니라면 깔끔하게 strings.ToLower를 사용하고…
'벤치마크' 카테고리의 다른 글
[Go] 파일 존재 여부에 대한 os.Stat() vs os.Open() (0) | 2023.02.25 |
---|