golang Указатели: получение и разыменовывание, скорость работы

Тут поговорим об указателях, указатели в целом должны куда-то "указывать", простой пример получения указателя:

a := 2 // целочисленная переменная
b := &a // переменная типа "указатель на int"

- причем тут b указывает именно на область памяти, где лежит значение a = 2

Разыменовывание указателя

Чтобы получить доступ к данным, на которые указывает переменная-указатель, необходимо разыменовать:

a := 2  // целочисленная переменная
b := &a // переменная типа "указатель на int"
fmt.Println(b)  // значение указателя (адрес памяти)
fmt.Println(*b) // значение, на которе указывает (разыменовывание)

Неинициллизированный указатель

Можно объявить и неициаллизированный указатель:

var c *int

Значение самого такого указателя распечатать можно, а вот то разыменовать его уже не получится:

var c *int

fmt.Println(c)  // распечатает nil
fmt.Println(*c) // упадет с паникой

-- при выполненении последней строки программа упадет с паникой (критической ошибкой).

Всё будет окей, если после объявление неинциллизированого указателя, мы получим конкретное значение:

var c *int
var a int = 1
c = &a
 // разыменовываем указатель, чтобы распечатать значение:
fmt.Println(*c)

Получение указателя через new()

Указатель можно получить и через ключевое слово new:

c := new(int) // с будет иметь тип указателя *int

fmt.Println(c)  // распечатает адрес 
fmt.Println(*c) // тут будет значение по умолчанию - 0 для int

-- тут не только будет выделена память, на которую мы получим указатель, но еще и значение будет установлено в пустое для данного типа (как при объявлении неинциллизированных переменных)

Звездочка как "указатель" и как разыменование - разные смыслы

Звездочка в коде для указателей в зависимости от ситуации имеет два смысла:

  1. *какойТоТип - как тип (напр при объявлении) - это указатель на какойТоТип, например:
    var c *int

    -- объявление переменной типа "указатель на int"

  2. *какаяТоПеременнаяУказатель - это разыменовывание какойТоПеременнойУказателя, например:
    fmt.Println(*c) 

    -- распечатка данных, лежащих по указателю, который хранится в переменной c (для чего требуется рахцменование)

Отсутствие адресной арифметики

Начать надо с того, что в Go нет адресной арифметики: это значит не можете прибавить какое-то значение к указателю и получить указатель на другую область памяти.
В Go указатель — это отдельный тип данных.

Указатель не является полноценной ссылкой (псевдонимом), которая указывает ровно туда же, куда и основная переменная.

Это значит, что если вы объявляете какую-то переменную как указатель на другую, то внутри этой переменной будет лежать значение адреса этой другой переменной (а не ёё собственное значение).

Пример 1

Также для лучшего понимания работы с указателями можно рассмотреть пример:

a := 2
b := &a
*b = 3  // a станет = 3
c := &a // новый указатель на переменную a

// получение указателя на переменнут типа int
// инициализировано значением по-умолчанию
d := new(int)
*d = 12
*c = *d // *c = 12 -> a = 12
*d = 13 // c и a не изменились

c = d
// теперь с указывает туда же, куда d
*c = 14 // с = 14 -> d = 14, a = 12

fmt.Println(a, *b, *c, *d)

Когда использователь указатели - ускорение работы программы

Указатели - не панацея!

Не стоит часто использовать указатели, поскольку это возможный источник паник. Если указатель nil и мы пытаемся его разыменовать, то возникает паника (см. примеры выше).
Также указатели — это нагрузка на сборщик мусора.
Применяйте указатели, когда вам действительно надо изменять данные, а не использовать их копию.

Фиксированный объем памяти для указателей

Указатели всегда занимают фиксированный объем памяти:

  • 4 байта для 32-битной архитектуры
  • и 8 байтов для 64-битной архитектуры.

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

На тему скорости работы указателей хорошо писал Джон Боднер в книге «Go: идиомы и паттерны проектирования»:

Когда есть прирост скорости: передача указателя (цитата Джона Боднера)

«В случае достаточно больших структур использование указателя на структуру в качестве входного параметра или возвращаемого значения дает некоторый прирост производительности. Время, необходимое на передачу указателя функции, является неизменным для данных любых размеров и составляет приблизительно одну наносекунду. Это вполне логично, поскольку размер указателя является одинаковым для всех типов данных. Передача функции значения занимает всё больше времени по мере увеличения размера данных и составляет примерно одну миллисекунду, когда размер значения доходит до 10 Мб. Возврат указателя дает более любопытный результат, чем возврат значения.


Когда скорость наоборот падает: возврат указателя для небольших структур (цитата Джона Боднера)

Когда размер структуры данных составляет менее одного мегабайта, возвращение указательного типа вместо значимого на самом деле снижает производительность. Так, например, возвращение структуры данных размером 100 байтов занимает около 10 наносекунд, а возвращение указателя на такую структуру данных — около 30 наносекунд.
Однако когда размер структуры данных превышает 1 мегабайт, использование указателей, наоборот, дает положительный эффект. Таким образом, для возвращения данных размером 10 Мб требуется почти 2 миллисекунды, а для возвращения указателя на такие данные — чуть больше половины миллисекунды.

В то же время имейте в виду, что это очень короткие промежутки времени. В подавляющем большинстве случаев различие в производительности между использованием указателей и значений никак не скажется на общей производительности программы. Но при передаче из одной функции в другую мегабайтов данных подумайте об использовании указателя, даже если эти данные не должны быть изменены.

Все приведенные здесь цифры были получены на компьютере с процессором i7-8700 и ОЗУ объемом 32 Гб. Вы можете провести собственное тестирование производительности, используя код с сайта GitHub https://oreil.ly/uVEin или https://github.com/learning-go-book/poin... »

Key Words for FKN + antitotal forum (CS VSU):

vedro-compota's picture

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

_____________
матфак вгу и остальная классика =)