
Общие функции на ломтиках: руководство, которое поможет вам понять
31 мая 2025 г.АсрезыПакет предоставляет функции, которые работают для ломтиков любого типа. В этом сообщении мы обсудим, как вы можете использовать эти функции более эффективно, понимая, как срезы представлены в памяти и как это влияет на сборщик мусора, и мы рассмотрим, как мы недавно скорректировали эти функции, чтобы сделать их менее удивительными.
СТип параметровМы можем писать функции какSlices.indexОдин раз для всех типов кусочков сопоставимых элементов:
// Index returns the index of the first occurrence of v in s,
// or -1 if not present.
func Index[S ~[]E, E comparable](s S, v E) int {
for i := range s {
if v == s[i] {
return i
}
}
return -1
}
Больше не нужно реализоватьIndex
Опять же для каждого типа элемента.
АсрезыПакет содержит много таких помощников для выполнения общих операций на ломтиках:
s := []string{"Bat", "Fox", "Owl", "Fox"}
s2 := slices.Clone(s)
slices.Sort(s2)
fmt.Println(s2) // [Bat Fox Fox Owl]
s2 = slices.Compact(s2)
fmt.Println(s2) // [Bat Fox Owl]
fmt.Println(slices.Equal(s, s2)) // false
Несколько новых функций (Insert
ВReplace
ВDelete
и т. д.) изменить срез. Чтобы понять, как они работают и как правильно их использовать, нам нужно изучить основную структуру срезов.
Среза - это вид части массива.Внутренне, срез содержит указатель, длину и емкость. Два ломтика могут иметь одинаковый базовый массив и могут просматривать перекрывающиеся части.
Например, этот ломтикs
является представлением о 4 элементах массива размера 6:
Если функция изменяет длину среза, пройденного в виде параметра, то она должна вернуть новый срез на вызывающего абонента. Основной массив может остаться прежним, если он не должен расти. Это объясняет почемудобавлятьиslices.Compact
вернуть значение, ноslices.Sort
, что просто переорчает элементы, нет.
Рассмотрим задачу удаления части среза. Перед дженериками стандартный способ удаления частиs[2:5]
от срезовs
должен был вызватьдобавлятьФункция для копирования конечной части над средней частью:
s = append(s[:2], s[5:]...)
Синтаксис был сложным и подверженным ошибкам, с участием подкладки и переменного параметра. Мы добавилиslices.deleteЧтобы упростить удаление элементов:
func Delete[S ~[]E, E any](s S, i, j int) S {
return append(s[:i], s[j:]...)
}
Однострочная функцияDelete
Более ясно выражает намерение программиста. Давайте рассмотрим ломтикs
длины 6 и емкость 8, содержащие указатели:
Этот вызов удаляет элементы наs[2]
Вs[3]
Вs[4]
от срезовs
:
s = slices.Delete(s, 2, 5)
Разрыв в индексах 2, 3, 4 заполняется смещением элементаs[5]
слева и установить новую длину на3
Полем
Delete
Не нужно выделять новый массив, так как он меняет элементы на месте. Нравитьсяappend
, он возвращает новый кусочек. Много других функций вslices
пакет следуйте по этому шаблону, включаяCompact
ВCompactFunc
ВDeleteFunc
ВGrow
ВInsert
, иReplace
Полем
При вызове этих функций мы должны рассмотреть исходный срез недействительным, потому что базовый массив был изменен. Было бы ошибкой назвать функцию, но игнорировать возвращаемое значение:
slices.Delete(s, 2, 5) // incorrect!
// s still has the same length, but modified contents
Проблема нежелательной жизни
Перед ходом 1,22,slices.Delete
не изменил элементы между новой и оригинальной длиной среза. В то время как возвращенный срез не будет включать эти элементы, «разрыв», созданный в конце оригинала, теперь непревзойденные срезы продолжали удерживать их. Эти элементы могут содержать указатели на большие объекты (изображение 20 МБ), и сборщик мусора не будет выпускать память, связанную с этими объектами. Это привело к утечке памяти, которая может привести к значительным проблемам с производительностью.
В приведенном выше примере мы успешно удаляем указателиp2
Вp3
Вp4
отs[2:5]
, сдвинув один элемент налево. Ноp3
иp4
все еще присутствуют в основном массиве, помимо новой длиныs
Полем Коллекционер мусора не вернет их. Менее очевидно,p5
не является одним из удаленных элементов, но его память все еще может протекать из -заp5
Указатель хранится в серой части массива.
Это может быть запутанным для разработчиков, если бы они не знали, что «невидимые» элементы все еще используют память.
Итак, у нас было два варианта:
- Либо сохранить эффективную реализацию
Delete
Полем Позвольте пользователям установить устаревшие указатели наnil
Сами, если они хотят убедиться, что указанные значения могут быть освобождены. - Или изменение
Delete
Чтобы всегда устанавливать устаревшие элементы на ноль. Это дополнительная работа, созданиеDelete
немного менее эффективно. Обновления указателей (устанавливая их наnil
) включает в себя коллекцию мусора объектов, когда они становятся иначе недоступными.
Не было очевидно, какой вариант был лучшим. Первый обеспечил производительность по умолчанию, а второй - по умолчанию, чтобы по умолчанию дало фругаренность памяти.
Исправление
Ключевым наблюдением является то, что «установить устаревшие указатели наnil
«Не так просто, как кажется.Compact
ВCompactFunc
ВDelete
ВDeleteFunc
ВReplace
«очистить хвост». Как хороший побочный эффект, когнитивная нагрузка уменьшается, и пользователям теперь не нужно беспокоиться об этих утечках памяти.
В Go 1.22, это то, на что выглядит память после вызова Delete:
Код, измененный в пяти функциях, использует новую встроенную функциюпрозрачный(GO 1.21) Чтобы установить устаревшие элементы на ноль значения типа элементаs
:
Нулевое значениеE
являетсяnil
когдаE
это тип указателя, среза, карты, чана или интерфейса.
Испытания провалы
Это изменение привело к некоторым тестам, которые прошли в GO 1.21, теперь сбой в GO 1.22, когда функции срезов используются неправильно. Это хорошие новости. Когда у вас есть ошибка, тесты должны сообщить вам.
Если вы проигнорируете возвращаемое значениеDelete
:
slices.Delete(s, 2, 3) // !! INCORRECT !!
тогда вы можете неправильно предположить, чтоs
не содержит никакого указателя NIL.Пример на игровой площадке GoПолем
Если вы проигнорируете возвращаемое значениеCompact
:
slices.Sort(s) // correct
slices.Compact(s) // !! INCORRECT !!
тогда вы можете неправильно предположить, чтоs
правильно отсортирован и уплотнен.ПримерПолем
Если вы назначите возвращаемое значениеDelete
к другой переменной и продолжайте использовать исходный ломтик:
u := slices.Delete(s, 2, 3) // !! INCORRECT, if you keep using s !!
тогда вы можете неправильно предположить, чтоs
не содержит никакого указателя NIL.ПримерПолем
Если вы случайно задерживаете переменную среза и продолжите использовать исходный ломтик:
s := slices.Delete(s, 2, 3) // !! INCORRECT, using := instead of = !!
тогда вы можете неправильно предположить, чтоs
не содержит никакого указателя NIL.ПримерПолем
Заключение
APIslices
Пакет-это чистое улучшение по сравнению с традиционным синтаксисом до генериков для удаления или вставки элементов.
Мы призываем разработчиков использовать новые функции, избегая при этом «GotChas», перечисленных выше.
Благодаря недавним изменениям в реализации, класс утечек памяти автоматически избегается, без каких -либо изменений в API и без дополнительной работы для разработчиков.
Дальнейшее чтение
Подпись функций вslices
Пакет сильно влияет на специфику представления срезов в памяти. Мы рекомендуем читать
- Go ломтики: использование и внутренние
- Массивы, ломтики: механика «добавления»
- Адинамический массивСтруктура данных
- Адокументациясрезов пакета
АОригинальное предложениеО обновлении устаревших элементов содержит много деталей и комментариев.
Валентин ДеЛиплс
ФотоБюшра СалкнаНеспособный
Эта статья доступна наБлог GOПод CC по лицензии 4,0.
Оригинал