go-mysql-org / go-mysql

a powerful mysql toolset with Go
MIT License
4.58k stars 976 forks source link

tune slice append performance #850

Closed hjweddie closed 7 months ago

hjweddie commented 7 months ago

I find some codes abount binlog, which is doing slices assignment or append could be simply changed, so that we can get some perfomance tuning. Hope that it could help

some corresponding benchmark code may be as follow:

package main_test

import (
    "testing"
)

func BenchmarkAppend(b *testing.B) {
    strs := []string{"a", "b", "c"}
    tmp := make([]string, 0, len(strs))

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        for _, str := range strs {
            tmp = append(tmp, str)
        }
    }
}

func BenchmarkAssign(b *testing.B) {
    strs := []string{"a", "b", "c"}
    tmp := make([]string, len(strs))

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        for i, str := range strs {
            tmp[i] = str
        }
    }
}
hjweddie commented 7 months ago

umm, I will check ci failure result

lance6716 commented 7 months ago
func BenchmarkAppend(b *testing.B) {
    strs := []string{"a", "b", "c"}
    tmp := make([]string, 0, len(strs))

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        for _, str := range strs {
            tmp = append(tmp, str)
        }
    }
}

for b.N larger than 1, your code will let tmp grow so it's slower.

hjweddie commented 7 months ago
func BenchmarkAppend(b *testing.B) {
  strs := []string{"a", "b", "c"}
  tmp := make([]string, 0, len(strs))

  b.ResetTimer()
  for i := 0; i < b.N; i++ {
      for _, str := range strs {
          tmp = append(tmp, str)
      }
  }
}

for b.N larger than 1, your code will let tmp grow so it's slower.

Agree it! I intend to reduce the overhead of slice growth

hjweddie commented 7 months ago

ci jobs pass

lance6716 commented 7 months ago

I mean your benchmark is not correct

func BenchmarkAppend(b *testing.B) {
    strs := []string{"a", "b", "c"}
    tmp := make([]string, 0, len(strs))

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
+       tmp = tmp[:0]
        for _, str := range strs {
            tmp = append(tmp, str)
        }
    }
}
goos: darwin
goarch: arm64
pkg: awesomeProject
BenchmarkAppend
BenchmarkAppend-8       373262626            3.161 ns/op
BenchmarkAssign
BenchmarkAssign-8       541436124            2.219 ns/op
PASS
hjweddie commented 7 months ago

package main_test

import ( "testing" )

func BenchmarkAppend(b *testing.B) { strs := []string{"a", "b", "c"} tmp := make([]string, 0, len(strs))

b.ResetTimer() for i := 0; i < b.N; i++ { for _, str := range strs { tmp = append(tmp, str) } } }

func BenchmarkAssign(b *testing.B) { strs := []string{"a", "b", "c"} tmp := make([]string, len(strs))

b.ResetTimer() for i := 0; i < b.N; i++ { for i, str := range strs { tmp[i] = str } } }

You are right. I put slice intialization in the loop and find slice assignment gets just a little more performance than append.

Because function parseSingleEvent could be called with high frequency in binlog parsing, I think it may be acceptable

func BenchmarkAppend(b *testing.B) {
    strs := []string{"a", "b", "c"}

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        tmp := make([]string, 0, len(strs))
        for _, str := range strs {
            tmp = append(tmp, str)
        }
    }
}

func BenchmarkAssign(b *testing.B) {
    strs := []string{"a", "b", "c"}

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        tmp := make([]string, len(strs))
        for i, str := range strs {
            tmp[i] = str
        }
    }
}
goos: darwin
goarch: arm64
pkg: test
BenchmarkAppend-8       29462312            40.81 ns/op
BenchmarkAssign-8       30202609            39.69 ns/op
PASS
ok      test    2.600s
hjweddie commented 7 months ago

If I try to make strs be a slice with a larger size, performance difference between assign and append will be larger too

func BenchmarkAppend(b *testing.B) {
    strs := make([]string, 0)
    for i := 0; i < 10000; i++ {
        strs = append(strs, "d")
    }

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        tmp := make([]string, 0, len(strs))
        for _, str := range strs {
            tmp = append(tmp, str)
        }
    }
}

func BenchmarkAssign(b *testing.B) {
    strs := make([]string, 0)
    for i := 0; i < 10000; i++ {
        strs = append(strs, "d")
    }

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        tmp := make([]string, len(strs))
        for i, str := range strs {
            tmp[i] = str
        }
    }
}
goos: darwin
goarch: arm64
pkg: test
BenchmarkAppend-8          42304         31042 ns/op
BenchmarkAssign-8          37588         29779 ns/op
PASS
ok      test    3.158s
hjweddie commented 7 months ago

I will check it later

hjweddie commented 7 months ago

I have changed the code as suggestion