From bc4cd92195b7d405c316d1511d436ae443a69d7e Mon Sep 17 00:00:00 2001 From: Elias Kauppi Date: Thu, 26 May 2022 23:13:19 +0300 Subject: [PATCH 1/6] Add benchmark for Any --- sliceutils_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/sliceutils_test.go b/sliceutils_test.go index 960dfc3..2240b22 100644 --- a/sliceutils_test.go +++ b/sliceutils_test.go @@ -1,11 +1,16 @@ package sliceutils import ( + "strings" "testing" "github.com/stretchr/testify/assert" ) +//////////////////////////////// +//********** TESTS ***********// +//////////////////////////////// + func TestAll(t *testing.T) { t.Run("All elements evaluate to true", func(t *testing.T) { slice := []int{1, 4, 6, 2, 3, 7} @@ -438,3 +443,31 @@ func TestReverseInPlace(t *testing.T) { assert.Nil(t, slice) }) } + +//////////////////////////////// +//******** BENCHMARKS ********// +//////////////////////////////// + +func BenchmarkAny(b *testing.B) { + slice := []string{"foo", "bar", "baz", "his", "her", "one", "log", "super", + "library", "functional function", "slice", "NOW", "hey"} + + b.Run("library", func(b *testing.B) { + for i := 0; i < b.N; i++ { + var _ = Any(slice, func(x string) bool { return strings.ContainsRune(x, rune('W')) }) + } + }) + + b.Run("for-loop", func(b *testing.B) { + for i := 0; i < b.N; i++ { + b := false + for _, x := range slice { + if strings.ContainsRune(x, rune('W')) { + b = true + break + } + } + var _ = b + } + }) +} From 1811063bc7eb4705791f32231653c44d30273b3f Mon Sep 17 00:00:00 2001 From: Elias Kauppi Date: Thu, 26 May 2022 23:46:28 +0300 Subject: [PATCH 2/6] Add benchmarking to CI --- .github/workflows/go.yml | 43 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 5fb5b26..0d9824b 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,6 +1,8 @@ name: CI -on: [push, pull_request] +on: + - push + - pull_request jobs: build: @@ -21,3 +23,42 @@ jobs: - name: Upload coverage to Codecov run: bash <(curl -s https://codecov.io/bash) + + benchmark: + name: Performance regression check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.18 + + # Run benchmark with `go test -bench` and stores the output to a file + - name: Run benchmark + run: go test -bench . | tee output.txt + + # Download previous benchmark result from cache (if exists) + - name: Download previous benchmark data + uses: actions/cache@v3 + with: + path: ./cache + key: ${{ runner.os }}-benchmark + + # Run `github-action-benchmark` action + - name: Store benchmark result + uses: benchmark-action/github-action-benchmark@v1 + with: + # What benchmark tool the output.txt came from + tool: 'go' + # Where the output from the benchmark tool is stored + output-file-path: output.txt + # Where the previous data file is stored + external-data-json-path: ./cache/benchmark-data.json + # Workflow will fail when an alert happens + fail-on-alert: true + # GitHub API token to make a commit comment + github-token: ${{ secrets.GITHUB_TOKEN }} + # Enable alert commit comment + comment-on-alert: true From 39d3af5c5f0b3a7cd3cad62e800530ca6f2296cb Mon Sep 17 00:00:00 2001 From: Elias Kauppi Date: Thu, 26 May 2022 23:51:47 +0300 Subject: [PATCH 3/6] Add benchmark for function All --- .github/workflows/go.yml | 3 ++- sliceutils_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 0d9824b..45d5cb7 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -6,6 +6,7 @@ on: jobs: build: + name: Build and test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -25,7 +26,7 @@ jobs: run: bash <(curl -s https://codecov.io/bash) benchmark: - name: Performance regression check + name: Benchmark runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/sliceutils_test.go b/sliceutils_test.go index 2240b22..eb26777 100644 --- a/sliceutils_test.go +++ b/sliceutils_test.go @@ -448,6 +448,30 @@ func TestReverseInPlace(t *testing.T) { //******** BENCHMARKS ********// //////////////////////////////// +func BenchmarkAll(b *testing.B) { + slice := []string{"boo", "bar", "baz", "hib", "heb", "obe", "lob", "suber", + "library", "functional function", "slice", "NOW", "hey"} + + b.Run("library", func(b *testing.B) { + for i := 0; i < b.N; i++ { + var _ = All(slice, func(x string) bool { return strings.ContainsRune(x, rune('b')) }) + } + }) + + b.Run("for-loop", func(b *testing.B) { + for i := 0; i < b.N; i++ { + b := true + for _, x := range slice { + if !strings.ContainsRune(x, rune('b')) { + b = false + break + } + } + var _ = b + } + }) +} + func BenchmarkAny(b *testing.B) { slice := []string{"foo", "bar", "baz", "his", "her", "one", "log", "super", "library", "functional function", "slice", "NOW", "hey"} From 4cbbf7be0a43bb599d2a0b8da79170c927608df1 Mon Sep 17 00:00:00 2001 From: Elias Kauppi Date: Fri, 27 May 2022 00:02:12 +0300 Subject: [PATCH 4/6] Remove for-loop benchmarking and rename benchmarks --- sliceutils_test.go | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/sliceutils_test.go b/sliceutils_test.go index eb26777..43b397a 100644 --- a/sliceutils_test.go +++ b/sliceutils_test.go @@ -452,46 +452,20 @@ func BenchmarkAll(b *testing.B) { slice := []string{"boo", "bar", "baz", "hib", "heb", "obe", "lob", "suber", "library", "functional function", "slice", "NOW", "hey"} - b.Run("library", func(b *testing.B) { + b.Run("Does all strings contain rune", func(b *testing.B) { for i := 0; i < b.N; i++ { var _ = All(slice, func(x string) bool { return strings.ContainsRune(x, rune('b')) }) } }) - - b.Run("for-loop", func(b *testing.B) { - for i := 0; i < b.N; i++ { - b := true - for _, x := range slice { - if !strings.ContainsRune(x, rune('b')) { - b = false - break - } - } - var _ = b - } - }) } func BenchmarkAny(b *testing.B) { slice := []string{"foo", "bar", "baz", "his", "her", "one", "log", "super", "library", "functional function", "slice", "NOW", "hey"} - b.Run("library", func(b *testing.B) { + b.Run("Does any string contain rune", func(b *testing.B) { for i := 0; i < b.N; i++ { var _ = Any(slice, func(x string) bool { return strings.ContainsRune(x, rune('W')) }) } }) - - b.Run("for-loop", func(b *testing.B) { - for i := 0; i < b.N; i++ { - b := false - for _, x := range slice { - if strings.ContainsRune(x, rune('W')) { - b = true - break - } - } - var _ = b - } - }) } From 7f76d566fa528d30a5d453d72705ee352be39455 Mon Sep 17 00:00:00 2001 From: Elias Kauppi Date: Sat, 9 Jul 2022 14:49:26 +0300 Subject: [PATCH 5/6] Fix typo --- sliceutils_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sliceutils_test.go b/sliceutils_test.go index fe66b83..ad14e5b 100644 --- a/sliceutils_test.go +++ b/sliceutils_test.go @@ -688,7 +688,7 @@ func BenchmarkAll(b *testing.B) { slice := []string{"boo", "bar", "baz", "hib", "heb", "obe", "lob", "suber", "library", "functional function", "slice", "NOW", "hey"} - b.Run("Does all strings contain rune", func(b *testing.B) { + b.Run("Do all strings contain rune", func(b *testing.B) { for i := 0; i < b.N; i++ { var _ = All(slice, func(x string) bool { return strings.ContainsRune(x, rune('b')) }) } From bc05ff353f3092be8adcef4bb3d0314bee9a0a85 Mon Sep 17 00:00:00 2001 From: Elias Kauppi Date: Mon, 15 Aug 2022 19:14:04 +0300 Subject: [PATCH 6/6] Add Filter and FilterInPlace, and optimize Filter to reserve capacity eagerly. --- sliceutils.go | 2 +- sliceutils_test.go | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/sliceutils.go b/sliceutils.go index ebcf44e..a4e54f7 100644 --- a/sliceutils.go +++ b/sliceutils.go @@ -114,7 +114,7 @@ func Filter[T any](slice []T, filterFn func(T) bool) []T { if slice == nil { return nil } - outSlice := make([]T, 0) + outSlice := make([]T, 0, len(slice)) for _, val := range slice { if filterFn(val) { outSlice = append(outSlice, val) diff --git a/sliceutils_test.go b/sliceutils_test.go index e540628..5c3bbda 100644 --- a/sliceutils_test.go +++ b/sliceutils_test.go @@ -725,3 +725,25 @@ func BenchmarkAny(b *testing.B) { } }) } + +func BenchmarkFilter(b *testing.B) { + b.Run("Filter to include strings shorter than 4 characters", func(b *testing.B) { + slice := []string{"foo", "bar", "baz", "his", "her", "one", "log", "super", + "library", "functional function", "slice", "NOW", "hey"} + + for i := 0; i < b.N; i++ { + var _ = Filter(slice, func(x string) bool { return len(x) < 4 }) + } + }) +} + +func BenchmarkFilterInPlace(b *testing.B) { + b.Run("Filter in-place to include strings shorter than 4 characters", func(b *testing.B) { + for i := 0; i < b.N; i++ { + slice := []string{"foo", "bar", "baz", "his", "her", "one", "log", "super", + "library", "functional function", "slice", "NOW", "hey"} + + FilterInPlace(&slice, func(x string) bool { return len(x) < 4 }) + } + }) +}