4 апреля 2020 г. Slice Allocation Sources
В блоге Go описывается, как использовать срезы. Давайте посмотрим на внутреннее устройство срезов.
Срез — это тип данных, который оборачивает массив.
Различия между срезом и массивом:
В исходниках Go срез представлен следующей структурой:
type slice struct {
array unsafe.Pointer
len int
cap int
}
len (длина) — текущая длина среза, cap (ёмкость) — количество элементов в базовом массиве.
Оба поля можно передать как параметры функции make:
s := make(
[]int,
10, // len
10, // cap
)
Ёмкость — основной параметр, отвечающий за выделение памяти в срезах. Она также отвечает за производительность append.
Давайте посмотрим на поведение среза при добавлении элементов:
a := []int{1}
b := a[0:1]
b[0] = 0
fmt.Println(a)
// [0]
a[0] = 1
fmt.Println(b)
// [1]
Мы создали срез b из среза a. Как видим, оба среза указывают на один и тот же базовый массив.
Добавим операцию append к одному из срезов:
a := []int{1}
b := a[0:1]
a = append(a, 2)
a[0] = 10
fmt.Println(a, b)
// [10 2] [0]
После append срезы имеют разные значения в первом элементе. Теперь срезы указывают на разные массивы.
Эту ситуацию можно понять, изучив исходники функции growslice.
При изменении ёмкости данные базового массива всегда копируются:
memmove(p, old.array, lenmem)
Теперь давайте посмотрим на реальные изменения ёмкости:
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
// Проверяем 0 < newcap для обнаружения переполнения
// и предотвращения бесконечного цикла.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Устанавливаем newcap в запрошенную cap, когда
// вычисление newcap привело к переполнению.
if newcap <= 0 {
newcap = cap
}
}
}
Когда длина среза < 1024, размер памяти удваивается.
В противном случае срез растёт на четверть.
Добавление элементов в срез оказывает значительное влияние на память:
В следующем примере единственное изменение — это ёмкость среза. Теперь после добавления элементов в срез изменения ёмкости не происходит. Оба среза по-прежнему указывают на один и тот же массив:
a := make([]int, 1, 2)
a[0] = 1
b := a[0:1]
b[0] = 0
fmt.Println(a, b)
// [0] [0]
a = append(a, 2)
a[0] = 10
fmt.Println(a, b)
// [10 2] [10]
В блоге Go описывается возможность использования другой функции append. Однако единственное, что мы можем сделать — это делать ещё более агрессивные выделения памяти, чем Go:
func AppendByte(slice []byte, data ...byte) []byte {
m := len(slice)
n := m + len(data)
if n > cap(slice) { // при необходимости перевыделяем
// выделяем вдвое больше необходимого для будущего роста.
newSlice := make([]byte, (n+1)*2)
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0:n]
copy(slice[m:n], data)
return slice
}
Следует быть осторожным, когда новый срез создаётся как часть старого. Весь старый базовый массив останется в памяти.
Go имеет богатые инструменты профилирования с самого начала — пакет pprof и go tool pprof. Давайте обсудим, почему профилирование полезно, как с ним работать и что нового в этой области.
Пакеты text/template и html/template являются частью стандартной библиотеки Go. Шаблоны Go используются во многих программах, написанных на Go — Docker, Kubernetes, Helm. Многие сторонние библиотеки интегрированы с шаблонами Go, например Echo. Знание синтаксиса шаблонов Go очень полезно.
Эта статья состоит из документации пакета text/template и нескольких решений автора. После описания синтаксиса шаблонов Go мы погрузимся в исходники text/template и html/template.
В Go у нас есть функциональность горутин из коробки. Мы можем запускать код параллельно. Однако в нашем параллельно выполняющемся коде мы можем работать с общими переменными, и не совсем понятно, как именно Go обрабатывает такие ситуации.
Read More → Map SourcesПрограммный интерфейс map в Go описан в блоге Go. Нам просто нужно вспомнить, что map — это хранилище ключ-значение, и оно должно извлекать значения по ключу как можно быстрее.
Read More → Map SourcesWhy PHP- and JavaScript-like regular expressions work with dot (".") work differently in GO.
Read More → Regular Expressions SourcesПочему регулярные выражения с точкой (".") работают по-другому в Go по сравнению с PHP и JavaScript.
Read More → Regular Expressions Sources