music: fix song build bottleneck

Fix redundant separator parsing obliterating loading performance.

If there are no separators configured, the parsing function would not
short-circuit and instead do a useless O(n) iteraton (including
escaping!), massively reducing performance.

Song build performance still isn't the best (blame intelligent
sorting), but it's definitely better now.
This commit is contained in:
Alexander Capehart 2023-06-01 20:13:52 -06:00
parent f9ccb831d8
commit a37df594e7
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
5 changed files with 15 additions and 3 deletions

View file

@ -10,6 +10,7 @@
- Albums implicitly linked only via "artist" tags are now placed in a special
"appears on" section in the artist view
- Album covers that are not 1:1 aspect ratio are no longer cropped
- Optimized library creation phase of the music loading process
#### What's Fixed
- Prevented options such as "Add to queue" from being selected on empty artists and playlists

View file

@ -365,6 +365,10 @@ constructor(
throw NoAudioPermissionException()
}
// TODO: Parallelize this even more aggressively. I can hypothetically connect all
// finalization steps (library, cache, playlists) into a single pipeline would need
// to change how I indicate progress however
// Start initializing the extractors. Use an indeterminate state, as there is no ETA on
// how long a media database query will take.
emitLoading(IndexingProgress.Indeterminate)

View file

@ -177,9 +177,12 @@ private class DeviceLibraryImpl(rawSongs: List<RawSong>, settings: MusicSettings
* @return A sorted list of [SongImpl]s derived from the [RawSong] that should be suitable for
* grouping.
*/
private fun buildSongs(rawSongs: List<RawSong>, settings: MusicSettings) =
Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING)
private fun buildSongs(rawSongs: List<RawSong>, settings: MusicSettings): List<Song> {
val songs = Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING)
.songs(rawSongs.map { SongImpl(it, settings) }.distinctBy { it.uid })
logD("Successfully built ${songs.size} songs")
return songs
}
/**
* Build a list of [Album]s from the given [Song]s.

View file

@ -174,6 +174,8 @@ private data class IntelligentKnownName(override val raw: String, override val s
override val sortTokens = parseTokens(sort ?: raw)
private fun parseTokens(name: String): List<SortToken> {
// TODO: This routine is consuming much of the song building runtime, find a way to
// optimize it
val stripped =
name
// Remove excess punctuation from the string, as those u

View file

@ -39,6 +39,8 @@ fun List<String>.parseMultiValue(settings: MusicSettings) =
this
}
// TODO: Remove the escaping checks, it's too expensive to do this for every single tag.
/**
* Split a [String] by the given selector, automatically handling escaped characters that satisfy
* the selector.
@ -106,7 +108,7 @@ fun List<String>.correctWhitespace() = mapNotNull { it.correctWhitespace() }
* @return A list of one or more [String]s that were split up by the user-defined separators.
*/
private fun String.maybeParseBySeparators(settings: MusicSettings): List<String> {
// Get the separators the user desires. If null, there's nothing to do.
if (settings.multiValueSeparators.isEmpty()) return listOf(this)
return splitEscaped { settings.multiValueSeparators.contains(it) }.correctWhitespace()
}