📕
Golang
  • Собеседование по golang
  • Вопросы собеседования
    • Список вопросов МТС
    • Список вопросов
    • Базовые вопросы по Golang
    • Go. Прорабатываем 25 основных вопросов собеседования
    • Функции
    • Структуры данных
    • Конкурентность и Параллелизм
    • Горутины
    • Примитивы синхронизации
    • Планировщик
    • Go: конкурентность и привязки к потокам в планировщике
    • Каналы
    • GC
  • спецификация
    • Спецификация Go: преобразования в и из строкового типа
    • переключатель типов (type switch)
    • for утверждения (for statements)
    • for утверждения с range условием
    • go утверждения (go statements)
    • select утверждения (select statements)
    • return утверждения (return statements)
    • continue утверждения (continue statements)
    • goto утверждения (goto statements)
    • fallthrough утверждения (fallthrough statements, утверждения "провала")
    • defer утверждения (defer statements)
    • встроенные функции, функция close
    • длина и емкость
    • аллокация, создание срезов (slice), карт (map) и каналов
    • добавление в срезы и копирование срезов
    • удаление элементов карты
    • обработка паники
    • начальная загрузка (bootstrapping)
    • пакеты
    • инициализация и выполнение программы, нулевое значение
    • инициализация пакета
    • выполнение программы
    • ошибки
    • паника во время выполнения (run-time panic)
  • Эффективный go
    • эффективный go
    • 50 оттенков go
    • Go: распространенные антипаттерны
    • Визуализация concurrency в Go с WebGL
  • требования для работы
    • Список навыков
  • habr
    • Изучаем многопоточное программирование в Go по картинкам
    • Go: конкурентность и привязки к потокам в планировщике
  • NP
    • Полиморфизм с интерфейсами в Golang
    • Объектно-ориентированное программирование в Golang
    • Владеешь merge  -  освой и rebase
  • ProgLib
    • Горутины
  • Untitled
  • Оптимизация
    • Go: Должен ли я использовать указатель вместо копии моей структуры?
  • Полезняшки
    • Using PostgreSQL JSONB with Go
Powered by GitBook
On this page

Was this helpful?

  1. Оптимизация

Go: Должен ли я использовать указатель вместо копии моей структуры?

PreviousUntitledNextUsing PostgreSQL JSONB with Go

Last updated 3 years ago

Was this helpful?

Перевод Иллюстрация, созданная для «A Journey With Go», из оригинального гофера, созданного Рене Френч. С точки зрения производительности систематическое использование указателей вместо копирования самой структуры для совместного использования структур многим Go разработчикам представляется наилучшим вариантом. Для того чтобы понять влияние использования указателя вместо копии структуры мы рассмотрим два варианта использования.

Интенсивное распределение данных

Давайте рассмотрим простой пример, когда вы хотите поделиться структурой для доступа к ее значениям:

type S struct {
  a, b, c int64
  d, e, f string
  g, h, i float64
}

Вот базовая структура, доступ к которой может быть разделен копией или указателем:

func byCopy() S {
  return S{
     a: 1, b: 1, c: 1,
     e: "foo", f: "foo",
     g: 1.0, h: 1.0, i: 1.0,
  }
}

func byPointer() *S {
  return &S{
     a: 1, b: 1, c: 1,
     e: "foo", f: "foo",
     g: 1.0, h: 1.0, i: 1.0,
  }
}

Основываясь на этих двух методах мы можем написать 2 бенчмарка. Первый — где структура передается копией:

func BenchmarkMemoryStack(b *testing.B) {
  var s S

  f, err := os.Create("stack.out")
  if err != nil {
     panic(err)
  }
  defer f.Close()

  err = trace.Start(f)
  if err != nil {
     panic(err)
  }

  for i := 0; i < b.N; i++ {
     s = byCopy()
  }

  trace.Stop()

  b.StopTimer()

  _ = fmt.Sprintf("%v", s.a)
}

Второй — очень похожий на первый — где структура передается по указателю:

func BenchmarkMemoryHeap(b *testing.B) {
  var s *S

  f, err := os.Create("heap.out")
  if err != nil {
     panic(err)
  }
  defer f.Close()

  err = trace.Start(f)
  if err != nil {
     panic(err)
  }

  for i := 0; i < b.N; i++ {
     s = byPointer()
  }

  trace.Stop()

  b.StopTimer()

  _ = fmt.Sprintf("%v", s.a)
}

Давайте запустим бенчмарки:

go test ./... -bench=BenchmarkMemoryHeap -benchmem -run=^$ -count=10 > head.txt && benchstat head.txt
go test ./... -bench=BenchmarkMemoryStack -benchmem -run=^$ -count=10 > stack.txt && benchstat stack.txt

Получаем вот такую статистику:

name          time/op
MemoryHeap-4  75.0ns ± 5%

name          alloc/op
MemoryHeap-4   96.0B ± 0%

name          allocs/op
MemoryHeap-4    1.00 ± 0%

------------------

name           time/op
MemoryStack-4  8.93ns ± 4%

name           alloc/op
MemoryStack-4   0.00B

name           allocs/op
MemoryStack-4    0.00

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

Все может быть даже еще хуже, если мы ограничим процессор до 1 с помощью GOMAXPROCS=1:

name        time/op
MemoryHeap  114ns ± 4%

name        alloc/op
MemoryHeap  96.0B ± 0%

name        allocs/op
MemoryHeap   1.00 ± 0%

------------------

name         time/op
MemoryStack  8.77ns ± 5%

name         alloc/op
MemoryStack   0.00B

name         allocs/op
MemoryStack    0.00

Если бенчмарк размещения в стеке не изменился, то показатель в куче уменьшился с 75ns/op до 114ns/op.

Интенсивные вызовы функций

Мы добавим два пустых метода в нашу структуру и немного адаптируем наши бенчмарки:

func (s S) stack(s1 S) {}

func (s *S) heap(s1 *S) {}

Бенчмарк с размещением в стеке создаст структуру и передаст ее копией:

func BenchmarkMemoryStack(b *testing.B) {
  var s S
  var s1 S

  s = byCopy()
  s1 = byCopy()
  for i := 0; i < b.N; i++ {
     for i := 0; i < 1000000; i++  {
        s.stack(s1)
     }
  }
}

И бенчмарк для кучи передаст структуру по указателю:

func BenchmarkMemoryHeap(b *testing.B) {
  var s *S
  var s1 *S

  s = byPointer()
  s1 = byPointer()
  for i := 0; i < b.N; i++ {
     for i := 0; i < 1000000; i++ {
        s.heap(s1)
     }
  }
}

Как и ожидалось, результаты сейчас совсем другие:

name          time/op
MemoryHeap-4  301µs ± 4%

name          alloc/op
MemoryHeap-4  0.00B

name          allocs/op
MemoryHeap-4   0.00

------------------

name           time/op
MemoryStack-4  595µs ± 2%

name           alloc/op
MemoryStack-4  0.00B

name           allocs/op
MemoryStack-4   0.00

Вывод

Ссылка:

Использование копии структуры оказалось в 8 раз быстрее, чем использование указателя на нее! Чтобы понять почему, давайте посмотрим на графики, генерируемые трассировкой: график для структуры, переданной копией график для структуры, переданной указателем Первый график довольно прост. Поскольку не используется куча, нет сборщика мусора и лишней горутины. Во втором случае использование указателей заставляет компилятор Go и работать сборщику мусора. Если мы увеличим масштаб графика, то увидим, что сборщик мусора занимает важную часть процесса: На этом графике видно, что сборщик мусора запускается каждые 4 мс. Если мы снова увеличим масштаб, мы можем получить подробную информацию о том, что именно происходит: Синие, розовые и красные полосы являются фазами сборщика мусора, а коричневые связаны с аллоцированием в куче (на графике помечено как «runtime.bgsweep»):

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

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

Использование указателя вместо копии структуры в go не всегда хорошо. Чтобы выбрать хорошую семантику для ваших данных, я настоятельно рекомендую прочитать пост о , написанной . Это даст вам лучшее представление о стратегиях, которые вы можете использовать со своими структурами и встроенными типами. Кроме того, профилирование использования памяти определенно поможет вам понять, что происходит с вашими аллокациями и кучей.

www.ardanlabs.com/blog/2018/12/garbage-collection-in-go-part1-semantics.html
эту статью
семантике значения/указателя
Биллом Кеннеди
Программирование*
Go*
Автор оригинала: Vincent Blanchon
перемещать переменную в кучу
Go: Должен ли я использовать указатель вместо копии моей структуры?Хабр
Logo
image
image
image
image
image