Beschleunigte Verzeichnisauflistung bei sehr vielen MP3s

Mittlerweile haben sich auf meiner SD-Karte 1500 MP3-Dateien in 150 Ordnern angesammelt.
Das Laden der Datei-Explorer Ansicht in der Weboberfläche ist durch die vielen Daten jetzt spürbar langsamer geworden > 2 Sekunden.

Es gab ja schon einige Versuche hier etwas zu optimieren wie schlankeres JSON, Übertragung über Websocket usw. Das Problem liegt aber im Dateisystem (FS) selbst. Die Funktion getNextFile() ist für das Auflisten von Verzeichnissen nicht optimiert. Es wird für jeden Dateinamen ein File-Objekt erzeugt und Dateieigenschaften wie Größe/Datum gelesen. Obwohl wir ja nur einen Dateinamen benötigen.
Auf Basis dieses Feature-Request habe ich das FS miit der Funktion getNextFileName() erweitert. Die gibt nur Dateinamen zurück ohne jeglichen Overhead. Siehe da, die Auflistung ist spürbar schneller (Faktor 20).

Ladezeit jetzt:

build filelist finished: 2041ms

Mit Patch:

build filelist finished: 98ms

Einen Pull-request dazu plane ich im Momemt nicht wg. laufenden Refaktoring der Weboberfläche durch @sonovice , auch riecht das Ändern des FS ein wenig nach „Hack“. Vielleicht schafft es der Feature-Request ins offizielle Arduino-ES32 Release… Wer es ausprobieren möchte, hier die Anleitung:

In platform.ini das erweiterte FS verknüpfen:

lib_deps =
	https://github.com/tueddy/FS.git#1.0.6
	;https://github.com/tueddy/FS.git		<- Für Arduino 2.0.5
	https://github.com/schreibfaul1/ESP32-audioI2S.git

Diese Zeilen in Web.cpp eintragen. Über den Compilerschalter #define FILEEXPLORER_SPEEDUP kann man dann umschalten.

Happy hacking!

5 „Gefällt mir“

Wie viele Dateien waren in dem Ordner?

Das habe ich gelesen, glaube aber nicht, dass diese Zeit für 1500 Files gültig ist. Zumal ja zur Zeit immer nur ein Verzeichnis geholt wird.

Meine Messung stammt direkt aus dem ESPuino Code hier.

Stimmt, es wird nur ein Verzeichnis gelesen (Beim ersten Aufruf der Weboberfläche das Root-Verzeichnis).
Das übertragene JSON ist recht klein und enthält nur die erste Ebene. Ich verwende SD_MMC mit einer aktuellen SD Karte. Also Alles optimal.

Habe heute Abend all meine SD-Karten rausgekramt und nochmal Messungen durchgeführt. Mit dem Espuino-Code Dateilisting des Root-Verzeichnisses ohne Patch (SD_MMC): Die Kingston Karte ist etwas betagt, alle anderen recht aktuell:

Samsung Evo 32GB, 406 Elemente (Alle Dateien + Ordner)
build filelist finished: 195 ms, 28 files in root

Kingston 8GB, 373 Elemente (Alle Dateien + Ordner)
build filelist finished: 592 ms, 41 files in root

Transcend Preminum 16GB, 1201 Elemente (Alle Dateien + Ordner)
build filelist finished: 931 ms, 82 files

Scandisk Ultra 16GB, 1569 Elemente (Alle Dateien + Ordner)
build filelist finished: 2152 ms, 146 files in root

Bei der Transcend Karte habe ich ein dann einziges Root-Verzeichnis erstellt und alle Dateien dort hinein verschoben:

Transcend Preminum 16GB
build filelist finished: 52 ms, 1 files in root

Dann habe ich ein Testprojekt erstellt das nur den Root listet ohne JSON usw. Habe dazu den Lolin D32 Pro mit dem SPI Anschluss verwendet, da wird es noch viel schlimmer:

Samsung Evo:
getNextFileName(), done reading root-directory 36 elemts in 18 ms = 0.50 ms/folder
openNextFile(), done reading root-directory 36 elements in 492 ms = 13.67 ms/elements

Kingston:
getNextFileName(), done reading root-directory 42 elemts in 29 ms = 0.69 ms/folder
openNextFile(), done reading root-directory 42 elements in 1119 ms = 26.64 ms/elements

Transcend:
getNextFileName(), done reading root-directory 84 elemts in 83 ms = 0.99 ms/folder
openNextFile(), done reading root-directory 84 elements in 6960 ms = 82.86 ms/elements

Samsung:
getNextFileName(), done reading root-directory 151 elemts in 68 ms = 0.45 ms/folder
openNextFile(), done reading root-directory 151 elements in 6872 ms = 45.51 ms/elements

Fazit: Das Arduino-FS ist grottenlangsam beim Auflisten eines Verzeichnis bei vielen Dateien , worst-case fast 7 Sekunden…
Je mehr Dateien/Ordner auf der SD vorhanden sind desto langsamer wird das. Die Messungen zeigen immer die gleichen Werte und weichen nur um wenige Millisekunden ab.
Alles über 0,5 Sekunden hakelt deutlich in der Weboberfläche!

Oder habe ich hier etwas übersehen oder einen Fehler gemacht?

Nicht dass wir uns hier falsch verstehen - das wollte ich mit meiner Rückfrage nicht behaupten :slight_smile:

Mir ging es darum ein Verhältnis zwischen Laufzeit und Anzahl der Files zu bekommen. Das der Patch mega gut ist und die Auflistung beschleunigt steht ja ausser Frage.
Hintergrund meiner Frage:
In meinem Fork ist es so, dass sofort die ersten Files übertragen werden, man also nicht auf die komplette Liste warten muss. Das Verzeichnis baut sich quasi auf. Das ist erst einmal cool, allerdings verliert man natürlich auch Zeit dadurch, dass mehrere kleine JSONs übertragen werden anstatt nur ein etwas größeres.
Wenn nun die gesamte Auflistung nur noch 100 ms dauert, könnte dieser Vorteil schnell kippen und es könnte sich schneller anfühlen, wenn doch das ganze Verzeichnis übertragen wird.
Das wollte ich gerne mal ausprobieren - dafür brauche ich aber die Information,
wie viele Files benötigen jetzt nur noch 98ms anstatt vorher 2041ms.

Also würde ich jetzt auch mal mit 150 Files testen wollen…

Moin @Christian,
ja das wäre spannend wie es sich bei Dir verhält, berichte mal hier.
Bei so 150 Dateien im Root sollte man das gut sehen können.

Ich meine jetzt einen Bug im Espuino-Code gefunden zu haben (Und den habe ich evt. selbst eingebaut :sunglasses:). Hier die betroffene Stelle:

Wenn keine Musik abgespielt wird soll feedTheDog() aufgerufen werden um einen Watchdog-Reset zu verhindern. Bei laufender Musik sorgt vTaskDelay(5) für ruckelfeies abspielen.

Beim Start/Reset der Box und sofortigen Aufruf der Weboberfläche:

gPlayProperties.pausePlay: 0
Ohne Patch: build filelist finished: 3076 ms, 82 files
Mit Patch: build filelist finished: 455 ms, 82 files

Jetzt lasse ich Musik laufen und bekomme

gPlayProperties.pausePlay: 0
Ohne Patch: build filelist finished: 3140 ms, 82 files
Mit Patch:build filelist finished: 459 ms, 82 files

Nun die Karte abziehen:

gPlayProperties.pausePlay: 1
Ohne Patch: build filelist finished: 2691 ms, 82 files
Mit Patch: build filelist finished: 59 ms, 82 files

Letzer Fall zeigt akzeptable Werte. Müsste beim Start gPlayProperties.pausePlay nicht mit True initialisiert sein und die Bedingung umgedreht sein ?

        if (gPlayProperties.pausePlay) {
            // time critical, avoid delay with many files on SD-card!
            feedTheDog(); 
        } else {
            // If playback is active this can (at least sometimes) prevent scattering
            vTaskDelay(portTICK_PERIOD_MS * 5);
        } 

Edit: Hatte die obigen Werte mit aktivierten Patch angegeben, jetzt mit und ohne.

Zunächst Frohe Ostern!

Dieses Thema ist zwar schon etwas älter aber ich möchte es mal neu beleben da es evt. auch für den DEV-Branch interessant sein und auch in diesem Thread nützlich sein könnte:

Links ein Video vom aktuellen Stand, rechts mit Optimierung (Faktor > 20 schnelleres Lesen). Habe im Video jeweils im Browser Reload gedrückt. Man sieht das es aktuell hakelt und sich die Weboberfläche einfach nicht flüssig anfühlt:

Slow Fast

Die Optimierung ist kompatibel mit Arduino 1.0.6 (master) & 2.0.7 (Dev-Branch). Es muss nur eine Zeile in Platform.ini eingetragen werden um das Dateisystem einzubinden. Damit wird dann automatisch HAS_FILEEXPLORER_SPEEDUP gesetzt und dann im Code verwendet. Die Änderungen beschränken sich auf wenige Zeilen. Ich verwende diesen Patch schon länger ohne Probleme.

Was noch fehlt ist das beschleunigte Lesen für die Playlist ohne Cache. Werden wohl < 10 Zeilen sein, möchte das jemand hier beisteuern? Wenn’s fertig & el jefe aus dem Urlaub zurück ist würde ich dann einen PR erstellen.

2 „Gefällt mir“

@laszloh willst du dir das im Zuge der Playlist-Optimierung anschauen oder soll ich da mal rein schauen?

@tueddy wirkt diese Verbesserung jetzt ganz generell, oder nur beim verwenden von getNextFileName?
Also konkret bei openNextFile?

getNextFileName() ist die optimierte Funktion für Verzeichnisauflistung und offiziell ab Arduino 2.0.5 verfügbar. Leider hat man vergessen eine Möglichkeit zu schaffen Verzeichnis oder Datei zu unterscheiden. Daher ist die Funktion so wie sie jetzt ist für uns unbrauchbar. Ich habe daher das FS angepasst und stelle einem Verzeichnis einen „/“ voran. Demo dazu hier

Bisherige Auflistung mit openNextFile() :

  File file = root.openNextFile();
  while(file){
    Serial.println(file.name() / file.path() );
    isDirectory = file.isDirectory());
    file = root.openNextFile();
  }

Beschleunigte Auflistung mit getNextFileName() :

  String filename = root.getNextFileName();
  while (filename != "") {
    Serial.println(filename);
    isDirectory = startsWith(fileName.c_str() , (char *)"//")
    filename = root.getNextFileName();
  }

@Joe91 Die Optimierung ist fertig für die Verzeichnisauflistung in der Web-UI (siehe Videos oben), fehlt aber noch für die Playlist-Generierung. Man könnte so ab hier und hier einsteigen. Ich würde den Patch jetzt unabhängig sehen & @laszloh kann das dann für seine Änderungen übernehmen. Oder gibt es noch andere Vorschläge?

1 „Gefällt mir“

Hi,
Ich hatte in dem anderen Thread schon geschrieben, ich bin kein großer Fan von impliziten „Zusätzen“ an Strings um Information zu transportieren. Das ist anfällig darauf, dass man vergisst, wieso etwas gemacht wurde und dann geändert wird. Plus, die Interpretation von „//“ ist abhängig von der Implementation (das hatte ich nicht gewusst, eigentlich dacht ich, das in POSIX/Linux 2 oder mehr / als einer gewertet werden → ich mag Stackoverflow, da lernt man immer was neues dazu :laughing:):

A pathname that begins with two successive slashes may be interpreted in an implementation-defined manner
POSIX Pathname Resolution specification

Nach ein wenig Stöbern würde ich vorschlagen, dass wir etweder ein std::pair<String, bool> (mit der Info Pathname / isDirectory) oder direkt den Pointer auf den struct dirent zurückgeben. Bei dem Pointer sind dann alle Informationen schon enthalten. Der relative Pfad (bzw nur der Dateiname) kommt uns in Zukunft zu gute. Aktuell unterstützen wir aber keine relativen Pfade, aber ein concat ist recht leicht zu implementieren.

Dazu würde ich eine neue Funktion in unserer FS.h empfehlen, das macht das Aktuell halten einfacher als wenn die Patches in bestehenden Funktionen eingreifen.

2 „Gefällt mir“

Finde das klingt gut. @tueddy was hälst du davon? Wenn das Filesystem schon direkt diese Information liefert kann der Code noch kompakter werden und man spart sich den doppelten Code in Web.cpp und SdCard.cpp.
Falls du es gut findest aber gerade nicht selbst machen willst kann ich mir das auch im Filesystem anschauen…

Der Plan war die Funktion brauchbar ins offizielle Arduino Release zu bekommen aber mein Gemeckere hat leider Nichts gebracht :frowning:

Habe Eure Anregungen aufgenommen & eine neue überladene Funktion gemacht:

String getNextFileName(boolean *isDir);

Die Web-Auflistung sieht dann so aus:

    #ifdef HAS_FILEEXPLORER_SPEEDUP  
	bool isDir;
    String MyfileName = root.getNextFileName(&isDir);
    while (MyfileName != "") {
        // ignore hidden folders, e.g. MacOS spotlight files
        if (!startsWith( MyfileName.c_str() , (char *)"/.")) {
            JsonObject entry = obj.createNestedObject();
            convertAsciiToUtf8(MyfileName.c_str(), filePath);
            std::string path = filePath;
            std::string fileName = path.substr(path.find_last_of("/") + 1);
            entry["name"] = fileName;
            entry["dir"].set(isDir);
        }
        MyfileName = root.getNextFileName(&isDir);
    }
    #else
    ..

Ich würde auch einen PR bei arduino-esp32 machen damit das offiziell werden kann. Eine überladene Funktion hat da evt. die besten Chancen. So oder so es sollte verwendbar sein.

4 „Gefällt mir“

Sehr cool! :+1:

Jupp, wenn du versuchst es in den Upstream zu bekommen macht es Sinn dass Interface nicht zu stark zu ändern. Mit der Funktion kann auch ich gut leben.

1 „Gefällt mir“

Würdest du das so wie so für deine Umbauten in SdCard.cpp anpassen oder soll ich die entsprechende Stelle Mal anschauen?

@Joe91 ist schon hier eingebaut, aber noch nicht getestet & Geschwindigkeit gemessen.

1 „Gefällt mir“

Sehr cool! Gerade mal aufgespielt und was mir sofort positiv auffällt, ist dass es nicht mehr die lange „Verzögerung“ beim ersten mal abspielen eines Ordners gibt (die Generierung des Playlist-Caches).

Ich kann auch im Betrieb nichts negatives bezüglich der Geschwindigkeit feststellen. Auch das löschen von Dateien und wieder neu hinzufügen funktioniert super (nur die aktuell spielende Datei sollte man nicht löschen :wink: )…

Konnte bei mir mit 50 Titeln in einem Ordner testen und konnte keinerlei „Verzögerung“ oder ähnlich beim Start festellen.

Gefällt mir sehr viel besser als mit den PlaylistCacheFiles! Hammer!!