golang performance tips!
- Phuong Le (@func25) tips: 3 and 4
- chat gpt tips: 1 and 2
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.
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
}
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
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
}