>  기사  >  백엔드 개발  >  Go String 분석

Go String 분석

Guanhui
Guanhui앞으로
2020-06-12 18:21:233054검색

Go String 분석

문자열이란 무엇인가요?

Go에서 문자열은 (아마도 비어 있을 수 있는) 변경할 수 없는 바이트 시퀀스입니다. 우리에게 여기서 핵심 단어는 불변입니다. 바이트 슬라이스는 변경 가능하기 때문에 문자열과 []바이트 간 변환에는 일반적으로 할당과 복사가 필요하며 이는 비용이 많이 듭니다.

내부적으로 Go의 문자열은 (현재) 길이와 문자열 데이터에 대한 포인터로 표시됩니다.

문자열 상주란 무엇입니까?

다음 코드를 고려하세요.

b := []byte("hello")
s := string(b)
t := string(b)

s와 t는 문자열이므로 둘 다 길이와 데이터 포인터를 갖습니다. 그들의 길이는 분명히 동일합니다. 데이터 포인터는 어떻습니까?

Go 언어는 직접 검색 방법을 제공하지 않습니다. 하지만 unsafe를 사용하여 조사할 수 있습니다.

func pointer(s string) uintptr {
    p := unsafe.Pointer(&s)
    h := *(*reflect.StringHeader)(p)
    return h.Data
}

(이 함수는 unsafe.Pointer를 반환해야 합니다. 자세한 내용은 Go 이슈 19367을 참조하세요.)

fmt.Println(pointer(s), 포인터(t))를 사용하면 다음을 얻을 수 있습니다. 4302664 4302632 정보와 같은 것입니다. 포인터는 서로 다릅니다. 두 개의 별도 데이터 복사본이 있습니다.

(연습 링크입니다. 한번 해보고 싶다면 "hello"를 "h"로 바꾸면 어떻게 될까요? 설명)

hello 데이터의 단일 복사본을 재사용하고 싶다고 가정해 보세요. 이것이 스트링 레지던시입니다. 문자열 레지던시에는 두 가지 장점이 있습니다. 분명한 장점은 데이터를 할당하고 복사할 필요가 없다는 것입니다. 또 다른 장점은 문자열 동일성 검사 속도가 빨라진다는 것입니다. 두 문자열의 길이가 같고 데이터 포인터가 같으면 바이트를 확인할 필요가 없습니다.

Go 1.14부터 Go는 대부분의 문자열을 유지하지 않습니다. 다른 형태의 캐싱과 마찬가지로 지속성에도 비용이 발생합니다. 동시성 안전성을 위한 동기화, 가비지 수집기 복잡성, 문자열이 생성될 때마다 실행되는 추가 코드 등이 있습니다. 그리고 캐싱과 마찬가지로 도움이 되기보다는 해로울 수 있는 상황이 있습니다. 단어가 두 번 나타나지 않는 사전의 단어를 처리하는 경우 문자열 지속성은 시간과 메모리를 낭비합니다.

수동 스트링 파킹

Go에서는 스트링을 수동으로 파킹할 수 있습니다. 우리에게 필요한 것은 아마도 map[[]byte]string 과 같은 것을 사용하여 주어진 바이트 슬라이스에서 재사용할 기존 문자열을 찾는 방법입니다. 조회가 성공하면 기존 문자열이 사용되며, 실패하면 나중에 사용할 수 있도록 문자열을 변환하고 저장합니다.

여기에는 단 한 가지 문제가 있습니다. []byte를 맵의 키로 사용할 수 없다는 것입니다.

오랜 기간 동안의 컴파일러 최적화 덕분에 map[string]string을 대신 사용할 수 있습니다. 여기서 최적화는 키가 변환된 바이트 조각인 맵 작업이 조회 중에 사용되는 새 문자열을 실제로 생성하지 않는다는 것입니다.

m := make(map[string]string)
b := []byte("hello")
s := string(b) // 分配了
_ = m[string(b)] // 不分配!

(모든 스위치 케이스에 부작용이 없을 때 스위치 문자열(b)과 같이 변환된 바이트 슬라이스가 사용 중에 수정되지 않는다는 것을 컴파일러가 증명할 수 있는 다른 경우에도 유사한 최적화가 적용됩니다.)

필요한 모든 코드 문자열을 주차하는 방법은 다음과 같습니다.

func intern(m map[string]string, b []byte) string {
    // 查找一个存在的字符串来重用
    c, ok := m[string(b)]
    if ok {
        // 找到一个存在的字符串
        return c
    }
    // 没有找到,所以制作一个并且存储它
    s := string(b)
    m[s] = s
    return s
}

쉽습니다

새로운 어려움(복잡성)

이 수동 주차 루틴이 문제를 주차한다는 점에 유의하세요. 호출 코드가 푸시됩니다. 지도에 대한 동시 액세스를 관리해야 합니다. 지도(및 그 안에 있는 모든 것)의 수명을 결정해야 하며 문자열이 필요할 때마다 지도 조회에 대한 추가 비용을 지불해야 합니다.

이러한 결정을 호출 코드에 적용하면 성능이 향상됩니다. 예를 들어, json을 map[string]interface{}로 디코딩한다고 가정해 보겠습니다. json 디코더는 동시적이지 않을 수 있습니다. 지도의 수명 주기는 json 디코더에 바인딩될 수 있습니다. 그리고 이 맵의 키는 자주 반복될 가능성이 높으며 이는 문자열 상주에 가장 적합한 경우이므로 추가 맵 조회 비용이 가치가 있습니다.

도우미 패키지

이런 복잡한 문제를 고려하지 않고 약간의 성능 저하를 기꺼이 받아들이고 도움이 될 수 있는 문자열 상주 코드가 있는 경우 다음 패키지가 있습니다:github.com/ 조샤리안/인턴.

작동 방식은 sync.Pool을 끔찍하게 남용했습니다. 상주 지도를 sync.Pool에 저장하고 필요에 따라 검색합니다. 이는 sync.Pool에 대한 액세스가 동시에 안전하기 때문에 동시 액세스 문제를 매우 잘 해결합니다. 일반적으로 sync.Pool의 콘텐츠는 결국 가비지 수집되기 때문에 주로 수명 문제를 해결합니다. (수명 관리 관련 자료는 Go 이슈 29696을 참조하세요.)

추천 튜토리얼: "PHP" "GO Tutorial"

위 내용은 Go String 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 learnku.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제