Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Итераторы в Go 1.23: зачем они нужны, как испол...

Lamoda Tech
December 20, 2024

Итераторы в Go 1.23: зачем они нужны, как использовать, и насколько они быстрые?

Паша Агалецкий, ведущий инженер в платформе Avito

Обсудим, зачем в Go добавили новый и весьма нетривиальный функционал — итераторы, также называемые range over funcs. Посмотрим на бенчмарки: быстрые ли итераторы? Быстрее каналов или медленнее? Как их использовать, где могут быть полезны, в чем была мотивация добавлять их в язык.

Lamoda Tech

December 20, 2024
Tweet

More Decks by Lamoda Tech

Other Decks in Programming

Transcript

  1. Москва | 2024 Москва | 2024 Агалецкий Павел, Авито Итераторы

    в Go 1.23 Зачем они, как использовать и насколько они быстрые
  2. Москва | 2024 Обо мне: • ведущий инженер в Авито;

    • 15+ лет опыта коммерческой разработки; • 5+ лет опыта разработки на Go. 2
  3. Москва | 2024 О чем доклад: • общие сведения об

    итераторах; • насколько они быстрые; • особые случаи; • для чего их можно использовать; • выводы. 3
  4. Москва | 2024 Виды for … range до go 1.23:

    slice := []int{1,2,3} for i := range slice { println(i) } 6
  5. Москва | 2024 Виды for … range до go 1.23:

    slice := []int{1,2,3} for i := range slice { println(i) } m := map[int]int{1:1,2:2} for i := range mapp { println(mapp[i]) } 7
  6. Москва | 2024 Виды for … range до go 1.23:

    slice := []int{1,2,3} for i := range slice { println(i) } m := map[int]int{1:1,2:2} for i := range mapp { println(mapp[i]) } intCh := make(chan int) for i := range <-intCh { println(i) } 8
  7. Москва | 2024 Виды for … range до go 1.23:

    slice := []int{1,2,3} for i := range slice { println(i) } m := map[int]int{1:1,2:2} for i := range mapp { println(mapp[i]) } intCh := make(chan int) for i := range <-intCh { println(i) } for i := range 10 { println(i) } 9
  8. Москва | 2024 Новые типы iter.Seq и iter.Seq2 package iter

    type Seq[V any] func(yield func(V) bool) type Seq2[K, V any] func(yield func(K, V) bool) 13
  9. Москва | 2024 Например func Range(yield func(int) bool) { for

    i := 0; i < 10; i++ { if !yield(i) { return } } } 14
  10. Москва | 2024 Может иметь параметры func Range(n int) func(yield

    func(int) bool) { return func(yield func(int) bool) { for i := 0; i < n; i++ { if !yield(i) { return } } } } 16
  11. Москва | 2024 Удобнее использовать iter.Seq func Range(n int) iter.Seq[int]

    { return func(yield func(int) bool) { for i := 0; i < n; i++ { if !yield(i) { return } } } } 18
  12. Москва | 2024 Надо проверять результат yield func Range(n int)

    iter.Seq[int] { return func(yield func(int) bool) { for i := 0; i < n; i++ { if !yield(i) { return } } } } 19
  13. Москва | 2024 Также есть iter.Seq2 func Range2(n int) iter.Seq2[int,

    string] { return func(yield func(int, string) bool) { for i := 0; i < n; i++ { if !yield(i, “val: “ + strconv.Itoa(i)) { return } } } } 20
  14. Москва | 2024 Используем его 21 for intV, strV :=

    range Range2(10) { println(intV, strV) } 0 val: 0 1 val: 1 2 val: 2 3 val: 3
  15. Москва | 2024 Или только второе 23 for _, strV

    := range Range2(10) { println(strV) } val: 0 val: 1 val: 2 val: 3
  16. Москва | 2024 Также и в Go 31 func Range()

    iter.Seq[int] { return func(yield func(int) bool) { for i := 0; i < 10; i++ { if !yield(i) { return } } } }
  17. Москва | 2024 TL;DR • стандартный способ итерации по значениям;

    • переключение контекста выполнения; • отсутствие новых ключевых слов; • по сути «синтаксический сахар». 32
  18. Москва | 2024 В итоге в 1.23 появились • новый

    пакет iter; • новые типы iter.Seq, iter.Seq2; • несколько новых вспомогательных функций iter.Pull, slices.All, maps.All, … 33
  19. Москва | 2024 Напишем бенчмарк 35 func backward(s []int) iter.Seq[int]

    { return func(yield func(int) bool) { for i := len(s) - 1; i >= 0; i-- { if !yield(s[i]) { return } } } }
  20. Москва | 2024 Напишем бенчмарк 36 func BenchmarkForLoop(b *testing.B) {

    slice := createBigSlice(1_000_000) b.Run("for_loop", func(b *testing.B) { for range b.N { total := 0 for i := len(slice) - 1; i >= 0; i-- { total += slice[i] } } })
  21. Москва | 2024 Напишем бенчмарк 37 b.Run("range", func(b *testing.B) {

    for range b.N { total := 0 for i := range slice { total += slice[len(slice)-i-1] } } })
  22. Москва | 2024 Напишем бенчмарк 38 b.Run("iterator", func(b *testing.B) {

    for range b.N { total := 0 for i := range backward(slice) { total += i } } })
  23. Москва | 2024 Итоги (go 1.23.2, mac m1 pro) 39

    BenchmarkForLoop/for_loop-8 314987 ns/op 0 B/op 0 allocs/op BenchmarkForLoop/range-8 527559 ns/op 0 B/op 0 allocs/op BenchmarkForLoop/iterator-8 504454 ns/op 0 B/op 0 allocs/op
  24. Москва | 2024 Например 43 for i := range Range(10)

    { println(i) } Range(10)(func(i int) bool { println(i) return true })
  25. Москва | 2024 Например 44 for i := range Range(10)

    { println(i) if i > 1 { break } } Range(10)(func(i int) bool { println(i) if i > 1 { return false } return true })
  26. Москва | 2024 Например 45 for i := range Range(10)

    { println(i) if i > 1 { break } } Range(10)(func(i int) bool { println(i) if i > 1 { return false } return true })
  27. Москва | 2024 Подопытный итератор 47 func tester() iter.Seq[int] {

    return func(yield func(int) bool) { for i := range 10 { if !yield(i) { return } } } } func TestTester(t *testing.T) { for v := range tester() { t.Log(v) } }
  28. Москва | 2024 Паника на одной из итераций 49 func

    tester() iter.Seq[int] { return func(yield func(int) bool) { for i := range 10 { if i > 0 { panic("panic in iterator") } if !yield(i) { return } } } }
  29. Москва | 2024 Паника работает как обычно 50 === RUN

    TestTester iterators_panic_test.go:10: 0 --- FAIL: TestTester (0.00s) panic: panic in iterator [recovered] panic: panic in iterator
  30. Москва | 2024 Мы можем выполнить recover 51 func TestTester(t

    *testing.T) { defer func() { if err := recover(); err != nil { t.Log("recovered: ", err) } }() for v := range tester() { t.Log(v) } }
  31. Москва | 2024 Мы можем выполнить recover 52 === RUN

    TestTester iterators_panic_test.go:16: 0 iterators_panic_test.go:11: recovered: panic in iterator --- PASS: TestTester (0.00s) PASS
  32. Москва | 2024 1. Паника в итераторе. 2. Паника в

    вызывающем коде. Особые случаи 53
  33. Москва | 2024 Паника в вызывающем коде 54 func TestTester(t

    *testing.T) { for v := range tester() { t.Log(v) if v > 0 { panic("panic in the loop") } } }
  34. Москва | 2024 Паника в вызывающем коде 55 === RUN

    TestTester iterators_panic_test.go:10: 0 iterators_panic_test.go:10: 1 --- FAIL: TestTester (0.00s) panic: panic in the loop [recovered] panic: panic in the loop
  35. Москва | 2024 Добавим recover в итератор 56 func tester(t

    testing.TB) iter.Seq[int] { return func(yield func(int) bool) { defer func() { if err := recover(); err != nil { t.Log("recovered: ", err) } }() for i := range 10 { …
  36. Москва | 2024 Паника рековерится… 57 === RUN TestTester iterators_panic_test.go:10:

    0 iterators_panic_test.go:10: 1 iterators_panic_test.go:21: recovered: panic in the loop --- FAIL: TestTester (0.00s)
  37. Москва | 2024 … но ругается! 58 === RUN TestTester

    iterators_panic_test.go:10: 0 iterators_panic_test.go:10: 1 iterators_panic_test.go:21: recovered: panic in the loop --- FAIL: TestTester (0.00s) panic: runtime error: range function recovered a loop body panic and did not resume panicking
  38. Москва | 2024 Итоги по паникам • вызывающий код может

    выполнить recover паники в итераторе; • итератор не может выполнять recover паники в loop body. 59
  39. Москва | 2024 1. Паника в итераторе. 2. Паника в

    вызывающем коде. 3. Отсутствие реакции на break. Особые случаи 60
  40. Москва | 2024 Отсутствие реакции на break 61 func tester()

    iter.Seq[int] { return func(yield func(int) bool) { for i := range 10 { if !yield(i) { return } } } }
  41. Москва | 2024 Отсутствие реакции на break 62 func tester()

    iter.Seq[int] { return func(yield func(int) bool) { for i := range 10 { if !yield(i) { // return } } } }
  42. Москва | 2024 Отсутствие реакции на break 63 func TestTester(t

    *testing.T) { for v := range tester() { t.Log(v) break } }
  43. Москва | 2024 Нельзя игнорировать break! 64 === RUN TestTester

    iterators_panic_test.go:10: 0 --- FAIL: TestTester (0.00s) panic: runtime error: range function continued iteration after function for loop body returned false
  44. Москва | 2024 1. Паника в итераторе. 2. Паника в

    вызывающем коде. 3. Отсутствие реакции на break. 4. yield в отдельной горутине. Особые случаи 65
  45. Москва | 2024 Будет использовать горутины 66 func tester() iter.Seq[int]

    { return func(yield func(int) bool) { wg := sync.WaitGroup{} for i := range 10 { wg.Add(1) go func() { defer wg.Done() if !yield(i) { return } }() } wg.Wait() } }
  46. Москва | 2024 Так нельзя! 67 === RUN TestTester panic:

    runtime error: range function continued iteration after loop body panic
  47. Москва | 2024 Но ведь мы не продолжаем… 68 ===

    RUN TestTester panic: runtime error: range function continued iteration after loop body panic
  48. Москва | 2024 Будем ждать завершения 71 func tester() iter.Seq[int]

    { return func(yield func(int) bool) { for i := range 10 { done := make(chan struct{}) go func() { defer close(done) if !yield(i) { return } }() <-done } } }
  49. Москва | 2024 Так можно! 72 === RUN TestTester iterators_test.go:10:

    0 iterators_test.go:10: 1 iterators_test.go:10: 2 …
  50. Москва | 2024 Пример 74 func QueryDB[V any](query string) iter.Seq[V]

    { return func(yield func(V) bool) { rows, err := performQuery(query) if err != nil { // what to do here? } defer rows.Close() for rows.Next() { var v V err := rows.Scan(&v) if err != nil { // what to do here? } if !yield(v) { return } } } }
  51. Москва | 2024 Пример 75 func QueryDB[V any](query string) iter.Seq[V]

    { return func(yield func(V) bool) { rows, err := performQuery(query) if err != nil { // what to do here? } defer rows.Close() for rows.Next() { var v V err := rows.Scan(&v) if err != nil { // what to do here? } if !yield(v) { return } } } }
  52. Москва | 2024 Первую ошибку выносим 76 func QueryDB[V any](query

    string) (iter.Seq[V], error) { rows, err := performQuery(query) if err != nil { return nil, err } return func(yield func(V) bool) { defer rows.Close() for rows.Next() { var v V err := rows.Scan(&v) if err != nil { // what to do here? } if !yield(v) { return } } }, nil }
  53. Москва | 2024 Для второй ошибки — iter.Seq2 77 func

    QueryDB[V any](query string) (iter.Seq2[V, error], error) { rows, err := performQuery(query) if err != nil { return nil, err } return func(yield func(V, error) bool) { defer rows.Close() for rows.Next() { var v V err := rows.Scan(&v) if !yield(v, err) { return } } }, nil }
  54. Москва | 2024 И тогда… 78 func TestErrors(t *testing.T) {

    row, err := QueryDB[User]("SELECT id, name FROM users") if err != nil { t.Fatal(err) } for user, err := range row { if err != nil { t.Log(err) break } t.Log(user) } }
  55. Москва | 2024 И тогда… 79 func TestErrors(t *testing.T) {

    row, err := QueryDB[User]("SELECT id, name FROM users") if err != nil { t.Fatal(err) } for user, err := range row { if err != nil { t.Log(err) break } t.Log(user) } }
  56. Москва | 2024 Посмотрим ещё раз на функцию 81 func

    QueryDB[V any](query string) (iter.Seq[V], error) { rows, err := performQuery(query) if err != nil { return nil, err } return func(yield func(V) bool) { defer rows.Close() for rows.Next() { var v V err := rows.Scan(&v) if err != nil { // what to do here? } if !yield(v) { return } } }, nil }
  57. Москва | 2024 Курсор закрывается в итераторе 82 func QueryDB[V

    any](query string) (iter.Seq[V], error) { rows, err := performQuery(query) if err != nil { return nil, err } return func(yield func(V) bool) { defer rows.Close() for rows.Next() { var v V err := rows.Scan(&v) if err != nil { // what to do here? } if !yield(v) { return } } }, nil }
  58. Москва | 2024 Что, если мы его не вызовем? 83

    func TestErrors(t *testing.T) { row, err := QueryDB[User]("SELECT id, name FROM users") if err != nil { t.Fatal(err) } panic("Ooops") for user, err := range row { if err != nil { t.Log(err) break } t.Log(user) } }
  59. Москва | 2024 Курсор не закроется 84 func TestErrors(t *testing.T)

    { row, err := QueryDB[User]("SELECT id, name FROM users") if err != nil { t.Fatal(err) } panic("Ooops") for user, err := range row { if err != nil { t.Log(err) break } t.Log(user) } }
  60. Москва | 2024 Исправим 85 func QueryDB[V any](query string) (iter.Seq[V],

    func(), error) { rows, err := performQuery(query) if err != nil { return nil, nil, err } return func(yield func(V) bool) { for rows.Next() { var v V err := rows.Scan(&v) if err != nil { // what to do here? } if !yield(v) { return } } }, func() { _ = rows.Close() }, nil }
  61. Москва | 2024 И тогда все будет хорошо 86 func

    TestErrors(t *testing.T) { row, clean, err := QueryDB[User]("SELECT id, name FROM users") if err != nil { t.Fatal(err) } defer clean() panic("Ooops") for user, err := range row { if err != nil { t.Log(err) break } t.Log(user) } }
  62. Москва | 2024 Например: умножение на число 90 func Multiply(seq

    iter.Seq[int], f int) iter.Seq[int] { return func(yield func(int) bool) { for i := range seq { if !yield(i * f) { return } } } }
  63. Москва | 2024 Например: фильтр по значению 91 func Filter(seq

    iter.Seq[int], by func(int) bool) iter.Seq[int] { return func(yield func(int) bool) { for i := range seq { if by(i) { if !yield(i) { return } } } } }
  64. Москва | 2024 Например: генератор диапазона 92 func Range(n int)

    iter.Seq[int] { return func(yield func(int) bool) { for i := range n { if !yield(i) { return } } } }
  65. Москва | 2024 И… пайплайн 93 func TestPipeline(t *testing.T) {

    elements := Range(1_000_000_000) elements = Multiply(elements, 2) elements = Filter(elements, func(i int) bool { return i%3 == 0 }) for i := range elements { println(i) } }
  66. Москва | 2024 Получаем список… 94 func TestPipeline(t *testing.T) {

    elements := Range(1_000_000_000) elements = Multiply(elements, 2) elements = Filter(elements, func(i int) bool { return i%3 == 0 }) for i := range elements { println(i) } }
  67. Москва | 2024 … умножаем… 95 func TestPipeline(t *testing.T) {

    elements := Range(1_000_000_000) elements = Multiply(elements, 2) elements = Filter(elements, func(i int) bool { return i%3 == 0 }) for i := range elements { println(i) } }
  68. Москва | 2024 … фильтруем… 96 func TestPipeline(t *testing.T) {

    elements := Range(1_000_000_000) elements = Multiply(elements, 2) elements = Filter(elements, func(i int) bool { return i%3 == 0 }) for i := range elements { println(i) } }
  69. Москва | 2024 … и выводим 97 func TestPipeline(t *testing.T)

    { elements := Range(1_000_000_000) elements = Multiply(elements, 2) elements = Filter(elements, func(i int) bool { return i%3 == 0 }) for i := range elements { println(i) } }
  70. Москва | 2024 Не очень красиво 98 func TestPipeline(t *testing.T)

    { elements := Range(1_000_000_000) elements = Multiply(elements, 2) elements = Filter(elements, func(i int) bool { return i%3 == 0 }) for i := range elements { println(i) } }
  71. Москва | 2024 Но красивее не сделать 99 func TestPipeline(t

    *testing.T) { elements := Range(1_000_000_000) elements = Multiply(elements, 2) elements = Filter(elements, func(i int) bool { return i%3 == 0 }) for i := range elements { println(i) } }
  72. Москва | 2024 Новые итераторы в stdlib (1.24) The bytes

    package adds several functions that work with iterators: • Lines; • SplitSeq; • SplitAfterSeq; • FieldsSeq; • FieldsFuncSeq. 101
  73. Москва | 2024 Собственно, корутины! Возможность переключаться между функциями без

    многозадачности: • это быстрее; • не требует синхронизации; • не требует менеджмента горутин. 102
  74. Москва | 2024 Использование в библиотеках • пока не самые

    популярные; • но дальше — больше! 104
  75. Москва | 2024 Когда смотришь — страшно 105 func Range(n

    int) func(yield func(int) bool) { return func(yield func(int) bool) { for i := 0; i < n; i++ { if !yield(i) { return } } } }
  76. Москва | 2024 Но все-таки… • выглядит как самая сложная

    часть языка; • полно puzzlers; • стандартизирует то, что было сделано везде по-своему; • посмотрим, вероятно, в течение года удастся их распробовать. 107