Share
Embed
Итераторы в Go 1.23: зачем они нужны, как использовать, и насколько они быстрые?
Паша Агалецкий, ведущий инженер в платформе Avito
Обсудим, зачем в Go добавили новый и весьма нетривиальный функционал — итераторы, также называемые range over funcs. Посмотрим на бенчмарки: быстрые ли итераторы? Быстрее каналов или медленнее? Как их использовать, где могут быть полезны, в чем была мотивация добавлять их в язык.
Lamoda Tech
December 20, 2024
Transcript
108 slides
-
Москва | 2024 Москва | 2024 Агалецкий Павел, Авито Итераторы
в Go 1.23 Зачем они, как использовать и насколько они быстрые -
Москва | 2024 Обо мне: • ведущий инженер в Авито;
• 15+ лет опыта коммерческой разработки; • 5+ лет опыта разработки на Go. 2 -
Москва | 2024 О чем доклад: • общие сведения об
итераторах; • насколько они быстрые; • особые случаи; • для чего их можно использовать; • выводы. 3 -
Москва | 2024 Виды for … range до go 1.23:
slice := []int{1,2,3} for i := range slice { println(i) } 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]) } 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) } 8 -
Москва | 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 -
Москва | 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 -
Москва | 2024 Например func Range(yield func(int) bool) { for
i := 0; i < 10; i++ { if !yield(i) { return } } } 14 -
Москва | 2024 Может использоваться в range for i :=
range Range { println(i) } 0 1 2 3 … 15 -
Москва | 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 -
Москва | 2024 Тогда можно их использовать for i :=
range Range(10) { println(i) } 0 1 2 3 … 17 -
Москва | 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 -
Москва | 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 -
Москва | 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 -
Москва | 2024 Используем его 21 for intV, strV :=
range Range2(10) { println(intV, strV) } 0 val: 0 1 val: 1 2 val: 2 3 val: 3 -
Москва | 2024 Используем первое значение 22 for intV :=
range Range2(10) { println(intV) } 0 1 2 3 -
Москва | 2024 Или только второе 23 for _, strV
:= range Range2(10) { println(strV) } val: 0 val: 1 val: 2 val: 3 -
Москва | 2024 Как в других языках 30 def createGenerator():
mylist = range(3) for i in mylist: yield i*i -
Москва | 2024 Также и в Go 31 func Range()
iter.Seq[int] { return func(yield func(int) bool) { for i := 0; i < 10; i++ { if !yield(i) { return } } } } -
Москва | 2024 TL;DR • стандартный способ итерации по значениям;
• переключение контекста выполнения; • отсутствие новых ключевых слов; • по сути «синтаксический сахар». 32 -
Москва | 2024 В итоге в 1.23 появились • новый
пакет iter; • новые типы iter.Seq, iter.Seq2; • несколько новых вспомогательных функций iter.Pull, slices.All, maps.All, … 33 -
Москва | 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 } } } } -
Москва | 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] } } }) -
Москва | 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] } } }) -
Москва | 2024 Напишем бенчмарк 38 b.Run("iterator", func(b *testing.B) {
for range b.N { total := 0 for i := range backward(slice) { total += i } } }) -
Москва | 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 -
Москва | 2024 Например 42 for i := range Range(10)
{ println(i) } -
Москва | 2024 Например 43 for i := range Range(10)
{ println(i) } Range(10)(func(i int) bool { println(i) return true }) -
Москва | 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 }) -
Москва | 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 }) -
Москва | 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) } } -
Москва | 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 } } } } -
Москва | 2024 Паника работает как обычно 50 === RUN
TestTester iterators_panic_test.go:10: 0 --- FAIL: TestTester (0.00s) panic: panic in iterator [recovered] panic: panic in iterator -
Москва | 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) } } -
Москва | 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 -
Москва | 2024 1. Паника в итераторе. 2. Паника в
вызывающем коде. Особые случаи 53 -
Москва | 2024 Паника в вызывающем коде 54 func TestTester(t
*testing.T) { for v := range tester() { t.Log(v) if v > 0 { panic("panic in the loop") } } } -
Москва | 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 -
Москва | 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 { … -
Москва | 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) -
Москва | 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 -
Москва | 2024 Итоги по паникам • вызывающий код может
выполнить recover паники в итераторе; • итератор не может выполнять recover паники в loop body. 59 -
Москва | 2024 1. Паника в итераторе. 2. Паника в
вызывающем коде. 3. Отсутствие реакции на break. Особые случаи 60 -
Москва | 2024 Отсутствие реакции на break 61 func tester()
iter.Seq[int] { return func(yield func(int) bool) { for i := range 10 { if !yield(i) { return } } } } -
Москва | 2024 Отсутствие реакции на break 62 func tester()
iter.Seq[int] { return func(yield func(int) bool) { for i := range 10 { if !yield(i) { // return } } } } -
Москва | 2024 Отсутствие реакции на break 63 func TestTester(t
*testing.T) { for v := range tester() { t.Log(v) break } } -
Москва | 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 -
Москва | 2024 1. Паника в итераторе. 2. Паника в
вызывающем коде. 3. Отсутствие реакции на break. 4. yield в отдельной горутине. Особые случаи 65 -
Москва | 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() } } -
Москва | 2024 Так нельзя! 67 === RUN TestTester panic:
runtime error: range function continued iteration after loop body panic -
Москва | 2024 Но ведь мы не продолжаем… 68 ===
RUN TestTester panic: runtime error: range function continued iteration after loop body panic -
Москва | 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 } } } -
Москва | 2024 Так можно! 72 === RUN TestTester iterators_test.go:10:
0 iterators_test.go:10: 1 iterators_test.go:10: 2 … -
Москва | 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 } } } } -
Москва | 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 } } } } -
Москва | 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 } -
Москва | 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 } -
Москва | 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) } } -
Москва | 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) } } -
Москва | 2024 5. Как вернуть ошибку? 6. Отсутствие запуска
итератора. Особые случаи 80 -
Москва | 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 } -
Москва | 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 } -
Москва | 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) } } -
Москва | 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) } } -
Москва | 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 } -
Москва | 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) } } -
Москва | 2024 Пайплайны 89 Get -> Process -> Filter
-> Process -> … -
Москва | 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 } } } } -
Москва | 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 } } } } } -
Москва | 2024 Например: генератор диапазона 92 func Range(n int)
iter.Seq[int] { return func(yield func(int) bool) { for i := range n { if !yield(i) { return } } } } -
Москва | 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) } } -
Москва | 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) } } -
Москва | 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) } } -
Москва | 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) } } -
Москва | 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) } } -
Москва | 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) } } -
Москва | 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) } } -
Москва | 2024 Новые итераторы в stdlib (1.24) The bytes
package adds several functions that work with iterators: • Lines; • SplitSeq; • SplitAfterSeq; • FieldsSeq; • FieldsFuncSeq. 101 -
Москва | 2024 Собственно, корутины! Возможность переключаться между функциями без
многозадачности: • это быстрее; • не требует синхронизации; • не требует менеджмента горутин. 102 -
Москва | 2024 Использование в библиотеках • пока не самые
популярные; • но дальше — больше! 104 -
Москва | 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 } } } } -
Москва | 2024 Зато использовать приятно 106 for i :=
range Range(10) { println(i) } -
Москва | 2024 Но все-таки… • выглядит как самая сложная
часть языка; • полно puzzlers; • стандартизирует то, что было сделано везде по-своему; • посмотрим, вероятно, в течение года удастся их распробовать. 107 -
Москва | 2024 Москва | 2024 Спасибо! Готов ответить на
ваши вопросы!