본문 바로가기
벤치마크

[Go] strings.ToLower() vs bytes.ToLower()

by dbadoy 2023. 2. 25.

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