refactor: reduce GC pressure by pre-allocating slices

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan 2024-11-19 07:45:24 -05:00
parent 3982ba7258
commit d229ff39e5
10 changed files with 325 additions and 262 deletions

View File

@ -84,7 +84,7 @@ type Albums []Album
// It assumes all albums have the same AlbumArtist, or else results are unpredictable. // It assumes all albums have the same AlbumArtist, or else results are unpredictable.
func (als Albums) ToAlbumArtist() Artist { func (als Albums) ToAlbumArtist() Artist {
a := Artist{AlbumCount: len(als)} a := Artist{AlbumCount: len(als)}
var mbzArtistIds []string mbzArtistIds := make([]string, 0, len(als))
for _, al := range als { for _, al := range als {
a.ID = al.AlbumArtistID a.ID = al.AlbumArtistID
a.Name = al.AlbumArtist a.Name = al.AlbumArtist

View File

@ -39,11 +39,11 @@ func ToLyrics(language, text string) (*Lyrics, error) {
text = str.SanitizeText(text) text = str.SanitizeText(text)
lines := strings.Split(text, "\n") lines := strings.Split(text, "\n")
structuredLines := make([]Line, 0, len(lines)*2)
artist := "" artist := ""
title := "" title := ""
var offset *int64 = nil var offset *int64 = nil
structuredLines := []Line{}
synced := syncRegex.MatchString(text) synced := syncRegex.MatchString(text)
priorLine := "" priorLine := ""
@ -105,7 +105,7 @@ func ToLyrics(language, text string) (*Lyrics, error) {
Value: strings.TrimSpace(priorLine), Value: strings.TrimSpace(priorLine),
}) })
} }
timestamps = []int64{} timestamps = nil
} }
end := 0 end := 0

View File

@ -108,11 +108,9 @@ type MediaFiles []MediaFile
// Dirs returns a deduped list of all directories from the MediaFiles' paths // Dirs returns a deduped list of all directories from the MediaFiles' paths
func (mfs MediaFiles) Dirs() []string { func (mfs MediaFiles) Dirs() []string {
var dirs []string dirs := slice.Map(mfs, func(m MediaFile) string {
for _, mf := range mfs { return filepath.Dir(m.Path)
dir, _ := filepath.Split(mf.Path) })
dirs = append(dirs, filepath.Clean(dir))
}
slices.Sort(dirs) slices.Sort(dirs)
return slices.Compact(dirs) return slices.Compact(dirs)
} }
@ -121,16 +119,16 @@ func (mfs MediaFiles) Dirs() []string {
// It assumes all mediafiles have the same Album, or else results are unpredictable. // It assumes all mediafiles have the same Album, or else results are unpredictable.
func (mfs MediaFiles) ToAlbum() Album { func (mfs MediaFiles) ToAlbum() Album {
a := Album{SongCount: len(mfs)} a := Album{SongCount: len(mfs)}
var fullText []string fullText := make([]string, 0, len(mfs))
var albumArtistIds []string albumArtistIds := make([]string, 0, len(mfs))
var songArtistIds []string songArtistIds := make([]string, 0, len(mfs))
var mbzAlbumIds []string mbzAlbumIds := make([]string, 0, len(mfs))
var comments []string comments := make([]string, 0, len(mfs))
var years []int years := make([]int, 0, len(mfs))
var dates []string dates := make([]string, 0, len(mfs))
var originalYears []int originalYears := make([]int, 0, len(mfs))
var originalDates []string originalDates := make([]string, 0, len(mfs))
var releaseDates []string releaseDates := make([]string, 0, len(mfs))
for _, m := range mfs { for _, m := range mfs {
// We assume these attributes are all the same for all songs on an album // We assume these attributes are all the same for all songs on an album
a.ID = m.AlbumID a.ID = m.AlbumID

View File

@ -1,6 +1,7 @@
package model_test package model_test
import ( import (
"path/filepath"
"time" "time"
"github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/conf"
@ -13,7 +14,7 @@ import (
var _ = Describe("MediaFiles", func() { var _ = Describe("MediaFiles", func() {
var mfs MediaFiles var mfs MediaFiles
Describe("ToAlbum", func() {
Context("Simple attributes", func() { Context("Simple attributes", func() {
BeforeEach(func() { BeforeEach(func() {
mfs = MediaFiles{ mfs = MediaFiles{
@ -270,6 +271,67 @@ var _ = Describe("MediaFiles", func() {
}) })
}) })
Describe("Dirs", func() {
var mfs MediaFiles
When("there are no media files", func() {
BeforeEach(func() {
mfs = MediaFiles{}
})
It("returns an empty list", func() {
Expect(mfs.Dirs()).To(BeEmpty())
})
})
When("there is one media file", func() {
BeforeEach(func() {
mfs = MediaFiles{
{Path: "/music/artist/album/song.mp3"},
}
})
It("returns the directory of the media file", func() {
Expect(mfs.Dirs()).To(Equal([]string{filepath.Clean("/music/artist/album")}))
})
})
When("there are multiple media files in the same directory", func() {
BeforeEach(func() {
mfs = MediaFiles{
{Path: "/music/artist/album/song1.mp3"},
{Path: "/music/artist/album/song2.mp3"},
}
})
It("returns a single directory", func() {
Expect(mfs.Dirs()).To(Equal([]string{filepath.Clean("/music/artist/album")}))
})
})
When("there are multiple media files in different directories", func() {
BeforeEach(func() {
mfs = MediaFiles{
{Path: "/music/artist2/album/song2.mp3"},
{Path: "/music/artist1/album/song1.mp3"},
}
})
It("returns all directories", func() {
Expect(mfs.Dirs()).To(Equal([]string{filepath.Clean("/music/artist1/album"), filepath.Clean("/music/artist2/album")}))
})
})
When("there are media files with empty paths", func() {
BeforeEach(func() {
mfs = MediaFiles{
{Path: ""},
{Path: "/music/artist/album/song.mp3"},
}
})
It("ignores the empty paths", func() {
Expect(mfs.Dirs()).To(Equal([]string{".", filepath.Clean("/music/artist/album")}))
})
})
})
})
var _ = Describe("MediaFile", func() { var _ = Describe("MediaFile", func() {
BeforeEach(func() { BeforeEach(func() {
DeferCleanup(configtest.SetupConfig()) DeferCleanup(configtest.SetupConfig())

View File

@ -140,11 +140,12 @@ func (r sqlRepository) buildSortOrder(sort, order string) string {
reverseOrder = "desc" reverseOrder = "desc"
} }
var newSort []string
parts := strings.FieldsFunc(sort, splitFunc(',')) parts := strings.FieldsFunc(sort, splitFunc(','))
newSort := make([]string, 0, len(parts))
for _, p := range parts { for _, p := range parts {
f := strings.FieldsFunc(p, splitFunc(' ')) f := strings.FieldsFunc(p, splitFunc(' '))
newField := []string{f[0]} newField := make([]string, 1, len(f))
newField[0] = f[0]
if len(f) == 1 { if len(f) == 1 {
newField = append(newField, order) newField = append(newField, order)
} else { } else {

View File

@ -143,7 +143,7 @@ func (s MediaFileMapper) albumArtistID(md metadata.Tags) string {
func (s MediaFileMapper) mapGenres(genres []string) (string, model.Genres) { func (s MediaFileMapper) mapGenres(genres []string) (string, model.Genres) {
var result model.Genres var result model.Genres
unique := map[string]struct{}{} unique := map[string]struct{}{}
var all []string all := make([]string, 0, len(genres)*2)
for i := range genres { for i := range genres {
gs := strings.FieldsFunc(genres[i], func(r rune) bool { gs := strings.FieldsFunc(genres[i], func(r rune) bool {
return strings.ContainsRune(conf.Server.Scanner.GenreSeparators, r) return strings.ContainsRune(conf.Server.Scanner.GenreSeparators, r)

View File

@ -84,7 +84,7 @@ func NewTag(filePath string, fileInfo os.FileInfo, tags ParsedTags) Tags {
func removeDuplicatesAndEmpty(values []string) []string { func removeDuplicatesAndEmpty(values []string) []string {
encountered := map[string]struct{}{} encountered := map[string]struct{}{}
empty := true empty := true
var result []string result := make([]string, 0, len(values))
for _, v := range values { for _, v := range values {
if _, ok := encountered[v]; ok { if _, ok := encountered[v]; ok {
continue continue
@ -300,7 +300,7 @@ func (t Tags) getFirstTagValue(tagNames ...string) string {
} }
func (t Tags) getAllTagValues(tagNames ...string) []string { func (t Tags) getAllTagValues(tagNames ...string) []string {
var values []string values := make([]string, 0, len(tagNames)*2)
for _, tag := range tagNames { for _, tag := range tagNames {
if v, ok := t.Tags[tag]; ok { if v, ok := t.Tags[tag]; ok {
values = append(values, v...) values = append(values, v...)
@ -311,7 +311,8 @@ func (t Tags) getAllTagValues(tagNames ...string) []string {
func (t Tags) getSortTag(originalTag string, tagNames ...string) string { func (t Tags) getSortTag(originalTag string, tagNames ...string) string {
formats := []string{"sort%s", "sort_%s", "sort-%s", "%ssort", "%s_sort", "%s-sort"} formats := []string{"sort%s", "sort_%s", "sort-%s", "%ssort", "%s_sort", "%s-sort"}
all := []string{originalTag} all := make([]string, 1, len(tagNames)*len(formats)+1)
all[0] = originalTag
for _, tag := range tagNames { for _, tag := range tagNames {
for _, format := range formats { for _, format := range formats {
name := fmt.Sprintf(format, tag) name := fmt.Sprintf(format, tag)

View File

@ -294,7 +294,7 @@ func (s *TagScanner) processChangedDir(ctx context.Context, refresher *refresher
// If track from folder is newer than the one in DB, select for update/insert in DB // If track from folder is newer than the one in DB, select for update/insert in DB
log.Trace(ctx, "Processing changed folder", "dir", dir, "tracksInDB", len(currentTracks), "tracksInFolder", len(files)) log.Trace(ctx, "Processing changed folder", "dir", dir, "tracksInDB", len(currentTracks), "tracksInFolder", len(files))
var filesToUpdate []string filesToUpdate := make([]string, 0, len(files))
for filePath, entry := range files { for filePath, entry := range files {
c, inDB := currentTracks[filePath] c, inDB := currentTracks[filePath]
if !inDB || fullScan { if !inDB || fullScan {

View File

@ -64,7 +64,6 @@ func walkFolder(ctx context.Context, rootPath string, currentFolder string, resu
} }
func loadDir(ctx context.Context, dirPath string) ([]string, *dirStats, error) { func loadDir(ctx context.Context, dirPath string) ([]string, *dirStats, error) {
var children []string
stats := &dirStats{} stats := &dirStats{}
dirInfo, err := os.Stat(dirPath) dirInfo, err := os.Stat(dirPath)
@ -77,11 +76,13 @@ func loadDir(ctx context.Context, dirPath string) ([]string, *dirStats, error) {
dir, err := os.Open(dirPath) dir, err := os.Open(dirPath)
if err != nil { if err != nil {
log.Error(ctx, "Error in Opening directory", "path", dirPath, err) log.Error(ctx, "Error in Opening directory", "path", dirPath, err)
return children, stats, err return nil, stats, err
} }
defer dir.Close() defer dir.Close()
for _, entry := range fullReadDir(ctx, dir) { entries := fullReadDir(ctx, dir)
children := make([]string, 0, len(entries))
for _, entry := range entries {
isDir, err := isDirOrSymlinkToDir(dirPath, entry) isDir, err := isDirOrSymlinkToDir(dirPath, entry)
// Skip invalid symlinks // Skip invalid symlinks
if err != nil { if err != nil {

View File

@ -100,7 +100,7 @@ func (c *simpleCache[K, V]) evictExpired() {
} }
func (c *simpleCache[K, V]) Keys() []K { func (c *simpleCache[K, V]) Keys() []K {
var res []K res := make([]K, 0, c.data.Len())
c.data.Range(func(item *ttlcache.Item[K, V]) bool { c.data.Range(func(item *ttlcache.Item[K, V]) bool {
if !item.IsExpired() { if !item.IsExpired() {
res = append(res, item.Key()) res = append(res, item.Key())
@ -111,7 +111,7 @@ func (c *simpleCache[K, V]) Keys() []K {
} }
func (c *simpleCache[K, V]) Values() []V { func (c *simpleCache[K, V]) Values() []V {
var res []V res := make([]V, 0, c.data.Len())
c.data.Range(func(item *ttlcache.Item[K, V]) bool { c.data.Range(func(item *ttlcache.Item[K, V]) bool {
if !item.IsExpired() { if !item.IsExpired() {
res = append(res, item.Value()) res = append(res, item.Value())