Статьи

Значение передачи по значению в GoLang

Теперь мы входим в некоторые из основных концепций! Как мы знаем, очень важно понимать влияние программы Go на машину.

Все передается по значению в Go, независимо от того, что вы передаете. То, что вы видите, это то, что вы получаете.

Каждая подпрограмма go (то есть путь выполнения) получает Stack, который является непрерывной памятью. Подпрограмме Go необходим стек, чтобы выполнить все необходимые выделения. Позже мы узнаем о рутине, она как нить, но намного легче.

Когда подпрограмма go выполняет функцию, она начинает получать фрагмент или часть памяти из выделенного стека.

Давайте попробуем понять это на простом примере

1
2
3
4
5
6
7
8
9
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">func main() {</span> func main () {</span>
 
  <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">counter := 0</span> счетчик: = 0</span>
 
  <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">counter++</span> счетчик ++</span>
 <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">fmt.Println("In main", counter)</span> fmt.Println ("В основном", счетчик)</span>
 <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">inc(counter)</span> вкл (счетчик)</span>
 <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">fmt.Println("After inc", counter)</span> fmt.Println ("После вкл.", счетчик)</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">}</span> }</span>
значение в GoLang

Функция может только читать / записывать в свой стековый фрейм, поэтому необходимы параметры функции.

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

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

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

Передача по значению необходима для безопасности и для рассуждения о коде, который отсутствует во многих языках.

Давайте рассмотрим все эти изменения, когда указатель или адрес переменной передается в функцию.

Попробуем понять, как выглядит кадр стека при выполнении кода ниже

1
2
3
4
5
6
7
8
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">func main() {</span> func main () {</span>
 
 <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">counter := 0</span> счетчик: = 0</span>
 
 <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">fmt.Println("Before pointer inc ", counter)</span> fmt.Println ("До указателя вкл.", счетчик)</span>
 <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">incByPointer(&counter)</span> incByPointer (& счетчик)</span>
 <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">fmt.Println("After pointer inc ", counter)</span> fmt.Println («После указателя inc», счетчик)</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">}</span> }</span>
значение в GoLang

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

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

Оператор звездочки (*) позволяет программе изменять переменную, которая находится за пределами своего собственного стекового фрейма. Эта переменная может быть в куче или в стеке функций вызывающей стороны.

Четкое разграничение при передаче значения и адреса значения является очень мощным средством, поскольку говорит, какая функция выполняет чтение по сравнению с записью.

Каждый раз, когда вы видите указатель (&), становится ясно, что в функции происходит некоторая мутация.

Никакая магическая модификация невозможна.

Наличие четкого различия имеет пару преимуществ:

— Компилятор может выполнить escape-анализ, чтобы определить, что распределяется между стеком и кучей. Это делает GC счастливым, потому что выделение стека обходится дешево, а в куче накладные расходы

— Когда копировать стоимость против стоимости акций. Это очень полезная вещь для больших значений, вы не хотите копировать 1 ГБ буфера для работы.

Go lang дает разработчикам возможность выбора компромисса, а не контроля.

Давайте рассмотрим еще один пример того, как работает распределение:

01
02
03
04
05
06
07
08
09
10
11
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">func allocateOnStack() stock {</span> func allocateOnStack () stock {</span>
 
 <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">google := stock{symbol: "GOOG", price: 1109}</span> google: = stock {символ: "GOOG", цена: 1109}</span>
 <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">return google</span> вернуть гугл</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">}</span> }</span>
 
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">func allocateOnHeap() *stock {</span> func allocateOnHeap () * stock {</span>
 
 <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">google := stock{symbol: "GOOG", price: 1109}</span> google: = stock {символ: "GOOG", цена: 1109}</span>
 <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">return &google</span> вернуться и Google</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">}</span> }</span>

Обе функции выше создают стоимость акций, но обратите внимание на тип возвращаемого значения: одно возвращает значение (allocateOnStack), а другое (allocateOnHeap) возвращает адрес.

Компилятор просматривает тип возвращаемого значения и принимает решение о том, что входит в стек по сравнению с кучей.

Таким образом, вы решили, что вы хотите бросить в GC против держать его счастливым.

У вас может возникнуть вопрос о стеке, например, насколько велик стек?

Каждая процедура Go начинается с размера стека 2 МБ, она небольшого размера и достаточно хороша, чтобы выдерживать множество вызовов функций.

В большинстве случаев 2 МБ — это хорошо, но если программа продолжает оказывать давление на память в стеке, она увеличивается, чтобы отрегулировать потребность только в конкретной программе Go.

Рост стека имеет стоимость выделения и копирования, это все равно что выделить новый массив и скопировать значение из предыдущего массива.

Приятной особенностью стековой памяти является то, что она контролируется GC и уменьшает размер стека, если использование стека составляет около 25%.

Go дает возможность компактного размещения памяти с использованием Struct и эффективного распределения памяти с использованием передачи по значению.

Все примеры, используемые в этом блоге, доступны по указателям github repo.

Смотрите оригинальную статью здесь: Значение передачи по значению в GoLang

Мнения, высказанные участниками Java Code Geeks, являются их собственными.