Эффективное соединение строк языка Go (2)

задняя часть Go переводчик контрольная работа

В предыдущей статье о конкатенации строкЭффективное соединение строк языка 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 строк, я не буду ее здесь тестировать, вы можете попробовать сами, чтобы увидеть, похоже ли это.

Из анализа этих двух недавних статей мы можем сделать вывод.

  1. +Конкатенация работает с короткими постоянными строками (явными, непеременными), потому что компилятор оптимизирует для нас.
  2. JoinЭто относительно равномерное сращивание, не очень гибкое.
  3. fmtа такжеbufferПринципиально не рекомендуется
  4. builderЭто отличный выбор с точки зрения производительности и гибкости.

Это здесь? Эта статья окончена, мне тоже пора спать. Но эффективная склейка строк еще не закончена, вышеописанное — это не предельная производительность, ее тоже можно оптимизировать, так что ждите третьей статьи.

Эта статья является оригинальной статьей, перепечатайте ее и укажите источник: «Всегда есть плохие люди, которые удаляют мое первоначальное описание, когда берут статью».flysnow_orgили сайтwww.flysnow.org/, и впервые прочитал последующие замечательные статьи. «Замечания против гнилых людей **...&*¥» Если вы считаете, что это хорошо, поделитесь им в кругу друзей, спасибо за вашу поддержку.

扫码关注