avatarDavid Lee

Summary

The article discusses the nuances between using range and index iteration in Go, including performance implications and the handling of string characters as rune types in range loops.

Abstract

The article "Secrets in Range and Index Iteration in Go" delves into the differences between iterating over arrays or slices using range versus index iteration in the Go programming language. It highlights that while range is convenient, it can be slower for large datasets due to the overhead of copying elements. The article also points out a common pitfall when using range on strings, as it iterates over Unicode code points (rune) rather than bytes, which can lead to unexpected behavior when expecting byte-sized characters. Through benchmarking tests, the author demonstrates that index iteration can be slightly faster than range, making it a better choice for performance-critical code. The article concludes by suggesting that while range offers readability and convenience, developers should consider using index iteration for better performance, especially in competitive scenarios like solving LeetCode problems or optimizing game leaderboard rankings.

Opinions

  • The author suggests that using index iteration can be more performant than range for iterating over large arrays or slices due to the overhead of element copying in range.
  • It is emphasized that when iterating over strings using range, one must be aware that characters are represented as rune types, which can handle multi-byte Unicode characters, unlike bytes.
  • The article provides a real-world example using a LeetCode problem to illustrate the performance difference between range and index iteration.
  • Benchmarking results indicate that index iteration is slightly faster than range, which could be significant in high-performance or competitive coding scenarios.
  • The author opines that while range is generally more readable and convenient, the performance benefit of index iteration may be preferred in certain cases, such as when aiming for top rankings in games or coding challenges.
  • The article subtly promotes an AI service, ZAI.chat, as a cost-effective alternative to ChatGPT Plus (GPT-4), suggesting it offers similar performance and functionality.

Secrets in Range and Index Iteration in Go

Do you use index or range to iterate an array or slice in Go?

Have you ever thought about their difference?

Why there is a range if there is a index iteration already?

Let’s have a quick easy problem in 20. Valid Parentheses

We know that sis a string, if you define:

    cache := map[byte]byte{
        '(': ')',
        '{': '}',
        '[': ']',
    }
    stack := []byte{}

Can you do below?

for _, sub := range s {
        if sub == '(' || sub == '{' || sub == '[' {
            stack = append(stack, sub)
}

No!

In Go, a string is a sequence of bytes, but when you iterate over a string using a range loop, the type of each character is rune, not byte. A rune in Go is an alias for int32 and represents a Unicode code point, which means it can handle any character in the Unicode standard, not just ASCII characters.

So we have to use:

    cache := map[rune]rune{
        '(': ')',
        '{': '}',
        '[': ']',
    }
    stack := []rune{}

If we use index iteration, we can define cache and stack as byte slice:

    cache := map[byte]byte{
        '(': ')',
        '{': '}',
        '[': ']',
    }
    stack := []byte{}
    for i := 0; i < len(s); i++ {
        sub := s[i]
}

Speed Also Matters

Let’s see another case and run some benchmarking tests:

package test

import (
 "testing"
)

func BenchmarkRange(b *testing.B) {
 arr := make([]int, 100000)
 for i := 0; i < b.N; i++ {
  sum := 0
  for _, v := range arr {
   sum += v
  }
 }
}

func BenchmarkIndex(b *testing.B) {
 arr := make([]int, 100000)
 for i := 0; i < b.N; i++ {
  sum := 0
  for j := 0; j < len(arr); j++ {
   sum += arr[j]
  }
 }
}

As to for _, v := range arr and for j := 0; j < len(arr); j++ , which is faster?

go test -bench=.

goos: darwin
goarch: arm64
pkg: tictactoe
BenchmarkRange-8           39072             29106 ns/op
BenchmarkIndex-8           41446             28905 ns/op
PASS
ok      tictactoe       3.134s

The benchmark results show that the time taken per operation for both the BenchmarkRange and BenchmarkIndex functions is quite similar, with BenchmarkIndex being slightly faster.

Here’s a breakdown of the results:

  • BenchmarkRange-8: The -8 indicates that the benchmark was run with a GOMAXPROCS value of 8. The function was called 39072 times, and each operation took about 29106 nanoseconds on average.
  • BenchmarkIndex-8: Similarly, this function was called 41446 times, and each operation took about 28905 nanoseconds on average.

These results suggest that using an index to iterate over the array is slightly faster than using range in this case.

Why?

In Go, using an index to iterate over an array or slice can be faster than using range, especially for larger arrays or slices. This is because range creates a copy of the element it's ranging over, which can add overhead.

If performance is a concern and you don’t need a copy of the elements you’re iterating over, it can be faster to use an index to iterate over the elements directly.

In some cases, the convenience and readability of range might outweigh the small performance gain from using an index.

Some cases you might want to be faster:

For solving this leetcode problem: 794. Valid Tic-Tac-Toe State

Let’s see the difference between using range (left) and index (right):

left: Using range iteration | right: using index iteration

If beats 100% matters to you, then you might want to choose the faster method. Think about a game leaderboard, you don’t want to just beat 60% of your competitors! You want to be on the top!

Golang
Go
Programming
Web Development
Software Development
Recommended from ReadMedium