Почему golang strings.Builder реализует String () вот так? - PullRequest
3 голосов
/ 26 мая 2020

Реализация:

// String returns the accumulated string.
func (b *Builder) String() string {
    return *(*string)(unsafe.Pointer(&b.buf))
}

Согласно моему тесту, преобразование байта [] в строку использует «копирование при записи», или компилятор генерирует инструкции глубокого копирования, если любой из них изменяет внутренний срез:

{
        a := []byte{'a'}
        s1 := string(a)
        a[0] = 'b'
        fmt.Println(s1) // a
    }

    {
        a := "a"
        b := []byte(a)
        b[0] = 'b'
        fmt.Println(a) // a
    }

Итак, что произойдет, если это будет реализовано, как показано ниже?

// String returns the accumulated string.
func (b *Builder) String() string {
    return string(b.buf)
}

Ответы [ 2 ]

8 голосов
/ 26 мая 2020

Вы можете просмотреть обсуждение списка изменений, который представил strings.Builder api здесь: https://go-review.googlesource.com/c/go/+/74931/4/src/strings/builder.go#30

Как и следовало ожидать, это обсуждение механики API, корректности, и эффективность.

Если вы замените код на string(b.buf), вы создадите копию созданной строки. Может случиться так, что компилятор оптимизирует копию в простых случаях преобразования байтового среза в строку, но очень маловероятно, что компилятор может сделать это здесь в целом (потому что для этого потребуется доказательство того, что буфер внутри построителя строк является больше никогда не использовался).

Обратите внимание, что код (стандартной библиотеки) выглядит опасно, потому что, если вы напишете это:

var b strings.Builder
b.WriteString("hello world")
c := b.String()
b.WriteString("a")
d := b.String()

, тогда c и d будут указывать к той же памяти. Но это нормально, потому что строки содержат длину своего буфера. И нет никакого способа изменить строку, потому что, хотя теоретически память, поддерживающая строку, доступна через buf в strings.Builder, единственный предоставленный API добавляется в резервную память.

8 голосов
/ 26 мая 2020

Учитывая достаточно большие строки, преобразование типа требует выделения памяти, тогда как преобразование с использованием небезопасного пакета не требует:

package main

import (
    "testing"
    "unsafe"
)

func BenchmarkConversion(b *testing.B) {
    buf := make([]byte, 16<<10)
    b.ResetTimer()

    for i := 0; i < b.N; i++ {
        var _ string = string(buf)
    }
}

func BenchmarkUnsafe(b *testing.B) {
    buf := make([]byte, 16<<10)
    b.ResetTimer()

    for i := 0; i < b.N; i++ {
        var _ string = *(*string)(unsafe.Pointer(&buf))
    }
}
$ go test -bench=. -benchmem
goos: linux
goarch: amd64
BenchmarkConversion-8            307087      3897 ns/op     16384 B/op     1 allocs/op
BenchmarkUnsafe-8            1000000000     0.299 ns/op         0 B/op     0 allocs/op
PASS
ok      _/tmp/tmp.KECLzZwkUn    1.579s
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...