Skip to content

Instantly share code, notes, and snippets.

@kehiy
Last active June 24, 2023 21:59
Show Gist options
  • Save kehiy/26bc86ae09c2dc8b104daa6c5ecf19ed to your computer and use it in GitHub Desktop.
Save kehiy/26bc86ae09c2dc8b104daa6c5ecf19ed to your computer and use it in GitHub Desktop.
Golang performance tips.

golang performance tips!

resources:

1. range instead of i

In Go, using range is typically faster and more efficient than indexing in a for loop.

When you use range in a for loop, the compiler generates code that efficiently iterates over the underlying data structure. In contrast, when you use indexing to access elements in a slice or array, the compiler generates additional instructions to calculate the offset of each element based on its index.

Additionally, range can be used with a variety of data structures such as arrays, slices, maps, and channels, whereas indexing is only applicable to arrays and slices.

Therefore, if you need to iterate over the elements of a collection in Go, it's generally recommended to use range whenever possible.

2. System calls are expensive

System calls are expensive operations that can slow down your program. Minimizing the number of system calls your program makes can improve its performance.

Let's say you need to read a large file and process its contents line by line. One way to do this is to use the bufio package to read the file line by line:

file, err := os.Open("largefile.txt")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

scanner := bufio.NewScanner(file)
for scanner.Scan() {
    // Process each line
}
if err := scanner.Err(); err != nil {
    log.Fatal(err)
}

While this code works, it makes a system call for every line in the file, which can be slow for large files. To minimize system calls, you can read the entire file into memory at once and then split it into lines:

file, err := ioutil.ReadFile("largefile.txt")
if err != nil {
    log.Fatal(err)
}

lines := bytes.Split(file, []byte("\n"))
for _, line := range lines {
    // Process each line
}

3. Avoid string concatenation

This code will create a new string on each iteration of the loop, which can be inefficient and may lead to poor performance.

It is generally more efficient to use the bytes.Buffer type to build strings rather than concatenating strings using the + operator.

	s := ""
	for i := 0; i < 100000; i++ {
		s += "x"
	}
	fmt.Println(s) 
  // 340.0ms to run

Thanks to Fede Vilensky suggestion, here is another solution: using strings.Builder. Its usage is similar to bytes.Buffer, but it provides even better performance

var builder strings.Builder

	for i := 0; i < 100000; i++ {
		builder.WriteString("x")
	}

	s := builder.String()
	fmt.Println(s)
  // 3.0ms to run

4. Pre-Allocating for slice, map

Allocating a slice with a capacity that is suitable for the number of elements it is expected to hold can improve performance in Go.

This is because allocating a slice with a larger capacity can reduce the number of times that the slice needs to be resized as elements are added.

func main() {
  // Allocate a slice with a small capacity
  start := time.Now()
  s := make([]int, 0, 10)
  for i := 0; i < 100000; i++ {
    s = append(s, i)
  }
  elapsed := time.Since(start)
  fmt.Printf("Allocating slice with small capacity: %v\n", elapsed) // 1.165208ms
  
  // Allocate a slice with a larger capacity
  start = time.Now()
  s = make([]int, 0, 100000)
  for i := 0; i < 100000; i++ {
    s = append(s, i)
  }
  elapsed = time.Since(start)
  fmt.Printf("Allocating slice with larger capacity: %v\n", elapsed) // 361.333µs
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment