From c96e8ab47c1dc59ad2934692b2fa2ff84c6c59c7 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Wed, 6 Sep 2023 14:44:42 +0200
Subject: [PATCH] db/simple/DirectorySave: optimize duplicate checks with
 std::set

This reduces the CPU usage for loading a large database by more than 50%.
---
 src/db/plugins/simple/DirectorySave.cxx | 24 ++++++++++++++++--------
 1 file changed, 16 insertions(+), 8 deletions(-)

diff --git a/src/db/plugins/simple/DirectorySave.cxx b/src/db/plugins/simple/DirectorySave.cxx
index 010957e9d..8a3410c01 100644
--- a/src/db/plugins/simple/DirectorySave.cxx
+++ b/src/db/plugins/simple/DirectorySave.cxx
@@ -17,6 +17,9 @@
 
 #include <fmt/format.h>
 
+#include <set>
+#include <string_view>
+
 #include <string.h>
 
 #define DIRECTORY_DIR "directory: "
@@ -109,9 +112,6 @@ ParseLine(Directory &directory, const char *line)
 static Directory *
 directory_load_subdir(LineReader &file, Directory &parent, std::string_view name)
 {
-	if (parent.FindChild(name) != nullptr)
-		throw FmtRuntimeError("Duplicate subdirectory '{}'", name);
-
 	Directory *directory = parent.CreateChild(name);
 
 	try {
@@ -139,20 +139,24 @@ directory_load_subdir(LineReader &file, Directory &parent, std::string_view name
 void
 directory_load(LineReader &file, Directory &directory)
 {
+	/* these sets are used to quickly check for duplicates,
+	   avoiding linear lookups */
+	std::set<std::string_view> children, songs;
+
 	const char *line;
 
 	while ((line = file.ReadLine()) != nullptr &&
 	       !StringStartsWith(line, DIRECTORY_END)) {
 		const char *p;
 		if ((p = StringAfterPrefix(line, DIRECTORY_DIR))) {
-			directory_load_subdir(file, directory, p);
+			auto *child = directory_load_subdir(file, directory, p);
+
+			const std::string_view name = child->GetName();
+			if (!children.emplace(name).second)
+				throw FmtRuntimeError("Duplicate subdirectory '{}'", name);
 		} else if ((p = StringAfterPrefix(line, SONG_BEGIN))) {
 			const char *name = p;
 
-			if (directory.FindSong(name) != nullptr)
-				throw FmtRuntimeError("Duplicate song '{}'",
-						      name);
-
 			std::string target;
 			bool in_playlist = false;
 			auto detached_song = song_load(file, name,
@@ -163,6 +167,10 @@ directory_load(LineReader &file, Directory &directory)
 			song->target = std::move(target);
 			song->in_playlist = in_playlist;
 
+			if (!songs.emplace(song->filename).second)
+				throw FmtRuntimeError("Duplicate song '{}'",
+						      name);
+
 			directory.AddSong(std::move(song));
 		} else if ((p = StringAfterPrefix(line, PLAYLIST_META_BEGIN))) {
 			const char *name = p;