Что происходит при конкатенации строк через оператор плюс в Go, как это влияет на производительность?
Что проверяют
Понимаете ли вы, что строка в Go неизменяема, и отличаете ли место, где + безвреден, от места, где он превращает линейную работу в квадратичную. Заодно смотрят, знаете ли про strings.Builder и не тащите ли его везде подряд.
Как отвечать
Строка это заголовок из указателя на байты и длины, сами байты менять нельзя. Поэтому a + b всегда выделяет новый буфер и копирует в него оба операнда.
Дальше деталь, которую обычно не знают: компилятор склеивает целое выражение за один раз. a + b + c + d это один вызов рантайма и одна аллокация, а не три, причём для короткого результата буфер может остаться на стеке. Конкатенация констант вообще выполняется на этапе компиляции.
Плохо становится в цикле:
var s string
for _, part := range parts {
s += part // новая строка и копирование всего накопленного на каждой итерации
}Здесь аллокация на каждой итерации, а объём копирования растёт квадратично от итоговой длины. Рабочий вариант:
var b strings.Builder
b.Grow(total)
for _, part := range parts {
b.WriteString(part)
}
s := b.String()Builder копит байты в растущем срезе и отдаёт строку без финального копирования. Если все куски уже лежат в срезе, ещё проще strings.Join. Для сборки с подстановками удобнее fmt.Sprintf, но он заметно дороже из-за рефлексии.
Где спотыкаются
Объявляют + медленным всегда и заводят Builder ради склейки двух-трёх известных строк. Выигрыша там нет, читаемость хуже. Критерий простой: число кусков заранее неизвестно или зависит от данных, значит Builder.
Второе: забывают Grow. Без резерва буфер растёт удвоением и перекладывается несколько раз, то есть часть выигрыша теряется. И Builder нельзя копировать после первой записи: передали его по значению в функцию, получили панику про копию ненулевого Builder.
Третье: путают длину в байтах и в символах. len(s) возвращает байты, срез по индексу режет байты и легко разрубает руну пополам, а конкатенация корректность UTF-8 не проверяет. Когда нужны символы, работать надо через []rune или пакет utf8.