1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-03-17 20:47:50 +02:00

perf(changelog): improve changelog sort performance (#5161)

This commit removes the unnecessary slice copy in `sortEntries`, and
replaces `sort.Slice` [^1] with the new `slices.SortFunc` [^2].

As recommended by the Go documentation, `slices.SortFunc` is generally
faster because it uses generic, whereas `sort.Slice` relies on
reflection, which incurs additional allocations

The benchmark result from the newly added `Benchmark_sortEntries` show
approximately a 64% performance improvement.


Benchmark result:

```
                     │   old.txt    │               new.txt               │
                     │    sec/op    │   sec/op     vs base                │
_sortEntries/asc-16    16.458µ ± 1%   5.958µ ± 1%  -63.80% (p=0.000 n=10)
_sortEntries/desc-16   17.675µ ± 1%   6.020µ ± 0%  -65.94% (p=0.000 n=10)
geomean                 17.06µ        5.989µ       -64.89%

                     │   old.txt    │               new.txt                │
                     │     B/op     │     B/op      vs base                │
_sortEntries/asc-16    3.164Ki ± 0%   1.164Ki ± 0%  -63.21% (p=0.000 n=10)
_sortEntries/desc-16   3.422Ki ± 0%   1.164Ki ± 0%  -65.98% (p=0.000 n=10)
geomean                3.290Ki        1.164Ki       -64.62%

                     │  old.txt   │              new.txt               │
                     │ allocs/op  │ allocs/op   vs base                │
_sortEntries/asc-16    68.00 ± 0%   25.00 ± 0%  -63.24% (p=0.000 n=10)
_sortEntries/desc-16   72.00 ± 0%   25.00 ± 0%  -65.28% (p=0.000 n=10)
geomean                69.97        25.00       -64.27%
```

[^1]: https://pkg.go.dev/sort#Slice
[^2]: https://pkg.go.dev/slices#SortFunc

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
This commit is contained in:
Eng Zer Jun 2024-10-01 00:27:29 +08:00 committed by GitHub
parent d2469666b8
commit b8aef100f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 57 additions and 22 deletions

View File

@ -2,12 +2,13 @@
package changelog
import (
"cmp"
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"slices"
"strings"
"github.com/caarlos0/log"
@ -211,7 +212,7 @@ func formatChangelog(ctx *context.Context, entries []string) (string, error) {
}
}
sort.Slice(groups, groupSort(groups))
slices.SortFunc(groups, groupSort)
for _, group := range groups {
if len(group.entries) > 0 {
result = append(result, group.title)
@ -221,10 +222,8 @@ func formatChangelog(ctx *context.Context, entries []string) (string, error) {
return strings.Join(result, newLineFor(ctx)), nil
}
func groupSort(groups []changelogGroup) func(i, j int) bool {
return func(i, j int) bool {
return groups[i].order < groups[j].order
}
func groupSort(i, j changelogGroup) int {
return cmp.Compare(i.order, j.order)
}
func filterAndPrefixItems(ss []string) []string {
@ -306,17 +305,16 @@ func sortEntries(ctx *context.Context, entries []string) []string {
if direction == "" {
return entries
}
result := make([]string, len(entries))
copy(result, entries)
sort.Slice(result, func(i, j int) bool {
imsg := extractCommitInfo(result[i])
jmsg := extractCommitInfo(result[j])
slices.SortFunc(entries, func(i, j string) int {
imsg := extractCommitInfo(i)
jmsg := extractCommitInfo(j)
compareRes := strings.Compare(imsg, jmsg)
if direction == "asc" {
return strings.Compare(imsg, jmsg) < 0
return compareRes
}
return strings.Compare(imsg, jmsg) > 0
return -compareRes
})
return result
return entries
}
func keep(filter *regexp.Regexp, entries []string) (result []string) {

View File

@ -9,14 +9,13 @@ import (
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/goreleaser/goreleaser/v2/internal/client"
"github.com/goreleaser/goreleaser/v2/internal/git"
"github.com/goreleaser/goreleaser/v2/internal/testctx"
"github.com/goreleaser/goreleaser/v2/internal/testlib"
"github.com/goreleaser/goreleaser/v2/pkg/config"
"github.com/goreleaser/goreleaser/v2/pkg/context"
"github.com/stretchr/testify/require"
)
func TestDescription(t *testing.T) {
@ -289,6 +288,33 @@ func TestChangelogSort(t *testing.T) {
}
}
func Benchmark_sortEntries(b *testing.B) {
ctx := testctx.New()
entries := []string{
"added feature 1",
"fixed bug 2",
"ignored: whatever",
"docs: whatever",
"something about cArs we dont need",
"feat: added that thing",
"Merge pull request #999 from goreleaser/some-branch",
"this is not a Merge pull request",
}
b.Run("asc", func(b *testing.B) {
ctx.Config.Changelog.Sort = "asc"
for range b.N {
sortEntries(ctx, entries)
}
})
b.Run("desc", func(b *testing.B) {
ctx.Config.Changelog.Sort = "desc"
for range b.N {
sortEntries(ctx, entries)
}
})
}
func TestChangelogInvalidSort(t *testing.T) {
ctx := testctx.NewWithCfg(config.Project{
Changelog: config.Changelog{
@ -814,12 +840,23 @@ func TestGroup(t *testing.T) {
},
}, testctx.WithCurrentTag("v0.0.2"), withFirstCommit(t))
require.NoError(t, Pipe{}.Run(ctx))
require.Contains(t, ctx.ReleaseNotes, "## Changelog")
require.Contains(t, ctx.ReleaseNotes, "### Bots")
require.Contains(t, ctx.ReleaseNotes, "### Features")
require.Contains(t, ctx.ReleaseNotes, "### Bug Fixes")
require.NotContains(t, ctx.ReleaseNotes, "### Catch nothing")
require.Contains(t, ctx.ReleaseNotes, "### Others")
require.Regexp(t, `## Changelog
### Features
\* \w+ feat: added that thing
### Bug Fixes
\* \w+ bug: Merge pull request #999 from goreleaser\/some-branch
### Bots
\* \w+ feat\(deps\): update foobar \[bot\]
### Others
\* \w+ first
\* \w+ this is not a Merge pull request
\* \w+ chore: something about cArs we dont need
\* \w+ docs: whatever
\* \w+ fix: whatever
\* \w+ ignored: whatever
\* \w+ fixed bug 2
\* \w+ added feature 1
`, ctx.ReleaseNotes)
}
func TestGroupBadRegex(t *testing.T) {