Go 프로그래머라면 Go 1.22, 특히 Go 1.23에서 반복자에 대해 여러 번 들어봤을 것입니다. . 하지만 아마도 당신은 왜 그것이 유용한지, 언제 사용해야 하는지 궁금해하면서 여전히 머리를 긁적일 것입니다. 글쎄, 당신은 바로 이곳에 있어요! Go에서 반복자가 어떻게 작동하고 왜 그렇게 유용한지 살펴보는 것부터 시작하겠습니다.
숫자 목록이 있고 각 숫자를 두 배로 늘리고 싶다고 상상해 보세요. 아래와 같은 간단한 기능을 사용하여 이 작업을 수행할 수 있습니다.
package main import ( "fmt" ) func NormalTransform[T1, T2 any](list []T1, transform func(T1) T2) []T2 { transformed := make([]T2, len(list)) for i, t := range list { transformed[i] = transform(t) } return transformed } func main() { list := []int{1, 2, 3, 4, 5} doubleFunc := func(i int) int { return i * 2 } for i, num := range NormalTransform(list, doubleFunc) { fmt.Println(i, num) } }
이 코드를 실행하면 다음과 같은 일이 발생합니다.
0 2 1 4 2 6 3 8 4 10
아주 간단하죠? 이는 모든 유형 T1의 목록을 가져와 각 요소에 변환 함수를 적용하고 모든 유형 T2의 변환된 목록이 포함된 새 목록을 반환하는 기본 일반 Go 함수입니다. 알면 이해하기 쉽다 제네릭!
하지만 반복자를 사용하여 이를 처리할 수 있는 다른 방법이 있다고 말하면 어떨까요?
이제 동일한 변환에 반복자를 사용하는 방법을 살펴보겠습니다.
package main import ( "fmt" ) func IteratorTransform[T1, T2 any](list []T1, transform func(T1) T2) iter.Seq2[int, T2] { return func(yield func(int, T2) bool) { for i, t := range list { if !yield(i, transform(t)) { return } } } } func main() { list := []int{1, 2, 3, 4, 5} doubleFunc := func(i int) int { return i * 2 } for i, num := range NormalTransform(list, doubleFunc) { fmt.Println(i, num) } }
실행하기 전에 Go 버전이 1.23인지 확인해야 합니다. 출력은 정확히 동일합니다.
0 2 1 4 2 6 3 8 4 10
그런데 여기서 반복자가 왜 필요한가요? 그게 더 복잡하지 않나요? 차이점을 파헤쳐 보겠습니다.
언뜻 보기에 반복자는 목록 변환과 같은 간단한 작업을 위해 약간 과도하게 설계된 것처럼 보입니다. 하지만 벤치마크를 실행하면 왜 고려할 가치가 있는지 알게 됩니다!
두 방법을 모두 벤치마킹하고 성능이 어떤지 살펴보겠습니다.
package main import ( "testing" ) var ( transform = func(i int) int { return i * 2 } list = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} ) func BenchmarkNormalTransform(b *testing.B) { for i := 0; i < b.N; i++ { NormalTransform(list, transform) } } func BenchmarkIteratorTransform(b *testing.B) { for i := 0; i < b.N; i++ { IteratorTransform(list, transform) } }
초기 벤치마크 결과는 다음과 같습니다.
BenchmarkNormalTransform-8 41292933 29.49 ns/op BenchmarkIteratorTransform-8 1000000000 0.3135 ns/op
와! 그것은 큰 차이입니다! 하지만 잠깐만요. 여기에는 약간의 불공평함이 있습니다. NormalTransform 함수는 완전히 변환된 목록을 반환하는 반면, IteratorTransform 함수는 목록을 아직 변환하지 않고 반복기만 설정합니다.
반복자를 완전히 반복하여 공정하게 만들어 보겠습니다.
func BenchmarkIteratorTransform(b *testing.B) { for i := 0; i < b.N; i++ { for range IteratorTransform(list, transform) { } } }
이제 결과가 더욱 합리적입니다.
BenchmarkNormalTransform-8 40758822 29.16 ns/op BenchmarkIteratorTransform-8 53967146 22.39 ns/op
알겠습니다. 반복자가 조금 더 빠릅니다. 왜? NormalTransform은 반환하기 전에 메모리(힙)에 변환된 전체 목록을 생성하는 반면, 반복자는 이를 반복할 때 변환을 수행하여 시간과 메모리를 절약합니다.
여기에서 스택 및 힙에 대해 자세히 알아보세요
반복자의 진정한 마법은 전체 목록을 처리할 필요가 없을 때 발생합니다. 목록을 변환한 후 숫자 4만 찾으려는 시나리오를 벤치마킹해 보겠습니다.
func BenchmarkNormalTransform(b *testing.B) { for i := 0; i < b.N; i++ { for _, num := range NormalTransform(list, transform) { if num == 4 { break } } } } func BenchmarkIteratorTransform(b *testing.B) { for i := 0; i < b.N; i++ { for _, num := range IteratorTransform(list, transform) { if num == 4 { break } } } }
결과가 말해줍니다:
package main import ( "fmt" ) func NormalTransform[T1, T2 any](list []T1, transform func(T1) T2) []T2 { transformed := make([]T2, len(list)) for i, t := range list { transformed[i] = transform(t) } return transformed } func main() { list := []int{1, 2, 3, 4, 5} doubleFunc := func(i int) int { return i * 2 } for i, num := range NormalTransform(list, doubleFunc) { fmt.Println(i, num) } }
이 경우 iterator가 훨씬 빠릅니다! 왜? 반복자는 전체 목록을 변환하지 않기 때문에 원하는 결과를 찾는 즉시 중지됩니다. 반면에 NormalTransform은 하나의 항목에만 관심이 있더라도 여전히 전체 목록을 변환합니다.
그럼 Go에서 반복자를 사용하는 이유는 무엇인가요?
Iterator: 익숙해지면 빠르고 유연하며 재미있습니다!
위 내용은 Go의 반복자 이해하기: 재미있는 다이빙!의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!