TGViewer
Всё о разработке | Леонид Ченский Всё о разработке | Леонид Ченский @leoscode · 732 subscribers
Post #55 510
РАЗБИРАЕМСЯ С ИТЕРАЦИЯМИ ПО СЛАЙСАМ В GO

В Go есть всеми знакомая конструкция для итерации по всем элементам слайса/массива:
for i, v := range slice {
// ...
}

i - текущий индекс элемента
v - копия текущего элемента слайса (slice[i])

И индекс, и значение мы можем опускать если не используем, с помощью _. Но зачем разработчики языка Go дали нам 2 разные возможности итерации по слайсам? И какой способ в итоге лучше использовать?

Рассмотрим пример:
s := []int{1, 2, 3}
for _, v := range s {
v++
}
fmt.Println(s)

Данный код выведет нам в консоль [1, 2, 3]. Почему так? В переменную v кладется копия элемента слайса и v никак не ссылается на элемент. В итоге изменение переменной v не приводит к изменению элементов слайса.

Обратный пример:
s := []int{1, 2, 3}
for i := range s {
s[i]++
}
fmt.Println(s)

Здесь уже программа нам выведет [2, 3, 4], поскольку конструкция s[i] получается неявной ссылкой на элемент слайса и тут уже нет никакого копирования, мы применяем оператор инкремента (++) непосредственно к самому элементу.

Ага, значит если нам надо менять содержимое элементов слайса, то тогда нужно использовать итерацию по индексу, а в остальных случаях по значению? - Не совсем так.

Допустим у нас элементы слайса не int, а большая структура:
type BigStruct struct { // size=1032 bytes
_ [1024]byte
Field int64
}

Попробуем проитерироваться по слайсу больших структур по индексу и по значению, сравнив оба подхода в бенчмарке:
func Benchmark(b *testing.B) {
const elems = 1000
s := make([]BigStruct, elems)
tmps := make([]int64, elems) // need to avoid compiler optimizations for loops

b.ResetTimer()

b.Run("iterate slice of structs by index", func(b *testing.B) {
for i := 0; i < b.N; i++ {
for idx := range s {
tmps[idx] += s[idx].Field
}
}
})

b.Run("iterate slice of structs by value", func(b *testing.B) {
for i := 0; i < b.N; i++ {
for idx, v := range s {
tmps[idx] += v.Field
}
}
})
}

Результат бенчмарка будет следующий:
Benchmark/iterate_slice_of_structs_by_index-10           1626810        735.2 ns/op        0 B/op        0 allocs/op
Benchmark/iterate_slice_of_structs_by_value-10 60226 19841 ns/op 0 B/op 0 allocs/op

Тут мы видим, что в данном случае итерация по индексу в РАЗЫ быстрее. Почему? Потому что Go при итерации по значению вынужден при каждой итерации копировать большую структуру в переменную. Чем больше объект, тем больше стоит его копирование. Если посмотреть такой код в профилировщике, то вы увидите, что работает runtime.duffcopy. Если такое встретится у вас, смело переходите на итерацию по индексу.

А если у нас слайс указателей?
s := make([]*BigStruct, elems)


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


В итоге для себя я сформировал следующие правила:
1. Если у нас слайс указателей - итерируюсь всегда по значению (если только нам не нужно проинициализировать пустой указатель).
2. Если у нас слайс структур - итерируюсь всегда по индексу (ни к чему нам лишние копирования).
3. Если у нас слайс чисел: нужно менять элементы - индекс, не нужно - значение.

Надеюсь эти правила помогут вам с code review 😉

#golang
  • 🔥 11
  • ❤ 7
  • 👍 2
  • ❤‍🔥 1
  • 👏 1
More from @leoscode
  1. Sep 15, 2026Помню как в детстве хотел такого же ассистента как Jarvis у Железного человека. Сейчас буд…
  2. Aug 24, 2026Гипотетическая история Представьте завтра все датацентры «вдруг» перестанут работать. Прив…
  3. Aug 4, 2026"Капризный день"
  4. Jul 17, 2026Пользуюсь случаем, напишу тут: ищу заряжененного Go разработчика к себе в команду. Ссылка…
  5. Jul 14, 2026Утро вторника началось не с кофе… Кто положил, признавайтесь!)
  6. Jul 1, 2026Согласны? 👍 Да 🗿Нет
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →