Следует ли в содержимом слайса объекты указывать через указатель на GO?

Рейтинг: 1Ответов: 1Опубликовано: 25.01.2023

Коллеги,

возник вопрос.... у меня есть набор объектов, которые я сохраняю в слайсе, например:

type MyObject struct {

}

var sl []MyObject

....

myObject := MyObject{}
sl = append(sl, myObject)


и на сколько правильно было бы сохранить в слайсе указатель на этот объект, а не его значение?

var sl []*MyObject

....

myObject := &MyObject{}
sl = append(sl, myObject)

И еще вопрос, если я делаю копирование слайса и в первом и во втором случае, то на сколько будет отличаться операция копирования по ресурсам?

slNew := sl // вариант 1:  var sl []*MyObject
slNew := sl // вариант 2:  var sl []MyObject

как еще дополнительный вопрос, если мы делаем итерацию по слайсу:

    for _, value := range slNew  // sl []*MyObject

или  
    for _, value := range slNew  // sl []MyObject

то в первом варианте у нас в value копируется только указатель на объект, а во втором варианте сам объект.....

вот собственно и вопрос, следует ли в содержимом слайса объекты указывать через указатель на GO?

Спасибо за помощь разобраться в данном вопросе!

Ответы

▲ 5Принят

ИМХО, размысшления об указателях и структурах - это тяжёлое наследие С++ с его конструктором копирования и семантикой сдвига. Go устроен принципиально иначе. Это язык со сборщиком мусора, значительно более простой семантикой и, как следствие, продвинутым оптимизатором. Вот правда, оптимизатор в Go творит какую-то запредельную жесть. Вы как-нибудь поглядите в ассемблер бинарника, собранного с оптимизацией и отключенными данными отладки: вообще ничего похожего на исходный код!

К чему это я. К тому, что в Go весьма продвинутый статический анализатор использования объекта (так называемый escape analysis). Если объект используется только для чтения, то его копия создаваться не будет. Это к вопросу о for _, value := range slNew. Если внутри цикла value не будет модифицироваться, то сто-пудово компилятор не будет создавать временный объект для хранения value.

Теперь про "копирование" слайса. Инструкция slNew := sl не копирует слайс. Эта инструкция создаёт небольшой объект типа SliceHeader, копирует в эту структуру указатель на собственно память с данными слайса, длину слайса и максимальный размер слайса, и всё. Если вам нужно создать именно дубликат слайса, то есть слайс с теми же данными, но расположенный в другой области памяти, вам нужно использовать встроенную функцию copy:

    slNew := make([]MyStruct, len(sl))
    copy(slNew, sl)

Эта пара операций, разумеется, гораздо быстрее в случае слайса указателей, просто в силу того, что указатель занимает всего 8 байт, а структуры могут быть гораздо большего размера. Копирование массивов из тысячи элементов: бенчмарк здесь

BenchmarkSlicePointers-8          432214          2962 ns/op        8192 B/op          1 allocs/op
BenchmarkSliceNoPointers-8        135276         10121 ns/op       32768 B/op          1 allocs/op

Бенчмарк показывает, что копирование указателей в три с половиной раза быстрее, чем копирование массива структур, но это напрямую вытекает из того, что в бенчмарке массив структур в четыре раза больше по размеру.

Кстати, благодаря тому, что при простом присваивании копируется только заголовок слайса, а не сам слайс, то при передаче в функции слайс передаётся фактически по ссылке: https://go.dev/play/p/0XpYmxDpr_w

package main

import "fmt"

func main() {
    var slice []int
    for i := 0; i < 10; i++ {
        slice = append(slice, i)
    }
    fmt.Println(slice)
    modify(slice, 10)
    fmt.Println(slice)
}

func modify(slice []int, value int) {
    slice[0] = value
}

Результат:

[1 2 3 4 5 6 7 8 9 10]
[10 2 3 4 5 6 7 8 9 10]

ИТОГО

Я всегда пользуюсь слайсами структур и стараюсь не связываться с указателями. Оптимизатор Go умнее меня, и лучше ему не мешать, ИМХО.