В предыдущей статье о конкатенации строкЭффективное соединение строк языка Go (1)В разделе мы демонстрируем различные методы сращивания строк и на примере проверяем их производительность.Благодаря сравнению мы обнаруживаем, что производительность высока.Builder
не работал как надо, вместо этого+
Нет сращивания, дажеstrings.Join
Производительность метода лучше, так в чем причина этого? Сегодня мы начинаем разгадывать их тайну и разгадывать тайну.
Бонус для всех, прежде чем мы начнем.В групповом бою Alibaba Cloud двойное 11, команда достигла сотен, и 30 лучших начали делить приз в миллион долларов..Присоединяйтесь сейчас, чтобы получить минимальную скидку 10% Облачный хост стоимостью 99 юаней в год уже может участвовать в распределении миллионов бонусов Команда Alibaba Cloud Double 11 занимает 30 место. автомобиль, старый водитель будет водить.
Преобразование функции сплайсинга
В конце предыдущей статьи я предложил 2 возможности: количество сращиваемых строк и размер склеиваемых строк.Теперь мы начнем доказывать эти два случая.Для удобства демонстрации мы будем использовать исходную функцию сплайсинга. Измените ее, чтобы принять[]string
Введите параметры, чтобы мы могли выполнить сращивание строк на нарезанном массиве, а вот модифицированные реализации всех методов сплайсинга.
func StringPlus(p []string) string{
var s string
l:=len(p)
for i:=0;i<l;i++{
s+=p[i]
}
return s
}
func StringFmt(p []interface{}) string{
return fmt.Sprint(p...)
}
func StringJoin(p []string) string{
return strings.Join(p,"")
}
func StringBuffer(p []string) string {
var b bytes.Buffer
l:=len(p)
for i:=0;i<l;i++{
b.WriteString(p[i])
}
return b.String()
}
func StringBuilder(p []string) string {
var b strings.Builder
l:=len(p)
for i:=0;i<l;i++{
b.WriteString(p[i])
}
return b.String()
}
в приведенной выше реализацииfor
цикл я не использовалfor range
, чтобы улучшить производительность, обратитесь к моемуОптимизация производительности языка Go — для исследования производительности Range.
прецедент
После того, как приведенная выше функция сращивания строк будет изменена, мы можем построить срезы разных размеров для теста сращивания строк. Чтобы смоделировать эффект последнего раза, мы сначала используем 10 строк размером слайс для теста сплайсинга, что похоже на тестовую ситуацию в предыдущей статье (тоже про сращивание 10 строк).
const BLOG = "http://www.flysnow.org/"
func initStrings(N int) []string{
s:=make([]string,N)
for i:=0;i<N;i++{
s[i]=BLOG
}
return s;
}
func initStringi(N int) []interface{}{
s:=make([]interface{},N)
for i:=0;i<N;i++{
s[i]=BLOG
}
return s;
}
Вот две функции, которые создают массив тестовых срезов, которые могут генерировать срезы размером N. секундаinitStringi
Функция возвращает[]interface{}
, который специально дляStringFmt(p []interface{})
Подготовлено функцией сплайсинга для уменьшения конверсий между типами.
С помощью этих двух функций для создания тестовых случаев мы можем построить наш тест производительности Go, мы начинаем с тестирования срезов размером 10.
func BenchmarkStringPlus10(b *testing.B) {
p:= initStrings(10)
b.ResetTimer()
for i:=0;i<b.N;i++{
StringPlus(p)
}
}
func BenchmarkStringFmt10(b *testing.B) {
p:= initStringi(10)
b.ResetTimer()
for i:=0;i<b.N;i++{
StringFmt(p)
}
}
func BenchmarkStringJoin10(b *testing.B) {
p:= initStrings(10)
b.ResetTimer()
for i:=0;i<b.N;i++{
StringJoin(p)
}
}
func BenchmarkStringBuffer10(b *testing.B) {
p:= initStrings(10)
b.ResetTimer()
for i:=0;i<b.N;i++{
StringBuffer(p)
}
}
func BenchmarkStringBuilder10(b *testing.B) {
p:= initStrings(10)
b.ResetTimer()
for i:=0;i<b.N;i++{
StringBuilder(p)
}
}
В каждой функции тестирования производительности мы будем вызыватьb.ResetTimer()
, это сделано для того, чтобы избежать проблемы отклонения эффекта теста производительности, вызванного разным временем подготовки тестового примера.Подробности см. в одной из моих статей.Боевые заметки по языку Go (двадцать два) | Тест Go.
мы бегаемgo test -bench=. -run=NONE -benchmem
Посмотреть Результаты.
BenchmarkStringPlus10-8 3000000 593 ns/op 1312 B/op 9 allocs/op
BenchmarkStringFmt10-8 5000000 335 ns/op 240 B/op 1 allocs/op
BenchmarkStringJoin10-8 10000000 200 ns/op 480 B/op 2 allocs/op
BenchmarkStringBuffer10-8 3000000 452 ns/op 864 B/op 4 allocs/op
BenchmarkStringBuilder10-8 10000000 231 ns/op 480 B/op 4 allocs/op
На этот раз мы можем видеть, что,+
Объединение номеров больше не является преимуществом, потому чтоstring
является неизменным, каждое сращивание будет генерировать новыйstring
, то есть будет выполняться выделение памяти.У нас сейчас слайсы размером 10.Каждую операцию нужно выделять 9 раз, занимая память, поэтому каждая операция занимает много времени, а естественная производительность низкая.
Эффективное соединение строк языка Go (2)
woohoo.fly snow.org/2018/11/05/…
Некоторые читатели могут помнить нашу последнюю статьюЭффективное соединение строк языка Go (1)середина,+
Тест производительности плюсового сшивания показывает только 2 выделения памяти, но мы используем несколько+
из.
func StringPlus() string{
var s string
s+="昵称"+":"+"飞雪无情"+"\n"
s+="博客"+":"+"http://www.flysnow.org/"+"\n"
s+="微信公众号"+":"+"flysnow_org"
return s
}
Давайте еще раз просмотрим этот код, их действительно много.+
Да, но выделений памяти всего 2, можно смело гадать, это 3 разаs+=
В результате тестируемые сегодня обычные срезы и слайсы длиной 10 имеют только 9 выделений памяти. Давайте посмотрим, как компилятор Go оптимизирует этот код, выполнив следующую команду:go build -gcflags="-m -m" main.go
, вывод содержит следующее:
can inline StringPlus as: func() string { var s string; s = <N>; s += "昵称:飞雪无情\n"; s += "博客:http://www.flysnow.org/\n"; s += "微信公众号:flysnow_org"; return s }
Теперь понятно с первого взгляда, на самом деле компилятор помог нам оптимизировать строки, оставив только 3s+=
На этот раз тест проводился длиной 10 ломтиков, и было также очевидно, что тестBuilder
чемBuffer
Производительность намного лучше, основная причина этой проблемы[]byte
а такжеstring
преобразование между,Builder
Точно решил проблему.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
Очень эффективное решение.
100 строк
Теперь давайте проверим ситуацию со склейкой строк 100. Для нашего вышеприведенного кода его очень легко преобразовать. Вот непосредственно тестовый код.
func BenchmarkStringPlus100(b *testing.B) {
p:= initStrings(100)
b.ResetTimer()
for i:=0;i<b.N;i++{
StringPlus(p)
}
}
func BenchmarkStringFmt100(b *testing.B) {
p:= initStringi(100)
b.ResetTimer()
for i:=0;i<b.N;i++{
StringFmt(p)
}
}
func BenchmarkStringJoin100(b *testing.B) {
p:= initStrings(100)
b.ResetTimer()
for i:=0;i<b.N;i++{
StringJoin(p)
}
}
func BenchmarkStringBuffer100(b *testing.B) {
p:= initStrings(100)
b.ResetTimer()
for i:=0;i<b.N;i++{
StringBuffer(p)
}
}
func BenchmarkStringBuilder100(b *testing.B) {
p:= initStrings(100)
b.ResetTimer()
for i:=0;i<b.N;i++{
StringBuilder(p)
}
}
Теперь запустите тест производительности, чтобы увидеть, какова производительность конкатенации 100 строк и какая функция наиболее эффективна.
BenchmarkStringPlus100-8 100000 19711 ns/op 123168 B/op 99 allocs/op
BenchmarkStringFmt100-8 500000 2615 ns/op 2304 B/op 1 allocs/op
BenchmarkStringJoin100-8 1000000 1516 ns/op 4608 B/op 2 allocs/op
BenchmarkStringBuffer100-8 500000 2333 ns/op 8112 B/op 7 allocs/op
BenchmarkStringBuilder100-8 1000000 1714 ns/op 6752 B/op 8 allocs/op
+
Число то же, что мы проанализировали выше, на этот раз это 99 выделений памяти, и производительность становится все хуже и хуже, в последующих тестах она будет исключена.
fmt
а такжеbufrer
Производительность не улучшилась, а продолжает снижаться. Остальное сильнееJoin
а такжеBuilder
.
1000 струн.
Испытательная сила аналогична приведенным выше главам, поэтому давайте посмотрим непосредственно на результаты испытаний.
BenchmarkStringPlus1000-8 1000 1611985 ns/op 12136228 B/op 999 allocs/op
BenchmarkStringFmt1000-8 50000 28510 ns/op 24590 B/op 1 allocs/op
BenchmarkStringJoin1000-8 100000 15050 ns/op 49152 B/op 2 allocs/op
BenchmarkStringBuffer1000-8 100000 23534 ns/op 122544 B/op 11 allocs/op
BenchmarkStringBuilder1000-8 100000 17996 ns/op 96224 B/op 16 allocs/op
Общая производительность аналогична производительности 100 строк, и производительность по-прежнему хорошая.Join
а такжеBuilder
. Суть этих двух методов несколько различна.
Если у вас есть существующие массивы и срезы, вы можете использовать их напрямую.Join
, а если нет, и погоня за гибким сращиванием, или выбратьBuilder
.Join
Он по-прежнему находится в готовых слайсах и массивах (ведь на склеивание в массив требуется время), и декомпозируется фиксированным образом, например, запятыми, пробелами и т. д., ограничения относительно велики.
резюме
Что касается склейки 10 000 строк, я не буду ее здесь тестировать, вы можете попробовать сами, чтобы увидеть, похоже ли это.
Из анализа этих двух недавних статей мы можем сделать вывод.
-
+
Конкатенация работает с короткими постоянными строками (явными, непеременными), потому что компилятор оптимизирует для нас. -
Join
Это относительно равномерное сращивание, не очень гибкое. -
fmt
а такжеbuffer
Принципиально не рекомендуется -
builder
Это отличный выбор с точки зрения производительности и гибкости.
Это здесь? Эта статья окончена, мне тоже пора спать. Но эффективная склейка строк еще не закончена, вышеописанное — это не предельная производительность, ее тоже можно оптимизировать, так что ждите третьей статьи.
Эта статья является оригинальной статьей, перепечатайте ее и укажите источник: «Всегда есть плохие люди, которые удаляют мое первоначальное описание, когда берут статью».
flysnow_org
или сайтwww.flysnow.org/, и впервые прочитал последующие замечательные статьи. «Замечания против гнилых людей **...&*¥» Если вы считаете, что это хорошо, поделитесь им в кругу друзей, спасибо за вашу поддержку.