Beschleunigte Verzeichnisauflistung bei sehr vielen MP3s

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!!

Das ist ja schon mal fein das es sich subjektiv schneller/flüssiger anfühlt!

Mein Messergebnis mit einem Ordner mit 24 Dateien:

#define CACHED_PLAYLIST_ENABLE aktiv aber noch keine Cachedatei vorhanden:

[ 561819 ]  Playlist-Generierung: uncached
[ 561880 ]  Anzahl gültiger Files/Webstreams: 24
[ 561880 ]  build playlist from SD-card finished: 82 ms

#define CACHED_PLAYLIST_ENABLE aktiv & playlistcache-Datei vorhanden::

[ 643103 ]  Gebe Speicher der alten Playlist frei.
[ 643103 ]  Freier Speicher nach Aufräumen: 110256
[ 643139 ]  Playlist-Generierung: cached
[ 643187 ]  Freier Speicher: 110060
[ 643189 ]  Anzahl gültiger Files/Webstreams: 24
[ 643189 ]  build playlist from SD-card finished: 86 ms

CACHED_PLAYLIST_ENABLE auskommentiert, also komplett ohne Cache:

[ 25516 ]  Playlist-Generierung: uncached
[ 25532 ]  Anzahl gültiger Files/Webstreams: 24
[ 25532 ]  build playlist from SD-card finished: 26 ms

Also ohne Cache scheint das tatsächlich noch schneller zu sein als mit Cache. Kann das jemand noch bestätigen?

2 „Gefällt mir“

Alles auf deinem Branch:
#define CACHED_PLAYLIST_ENABLE aktiv aber noch keine Cachedatei vorhanden:

[ 71833 ]  Playlist-Generierung: uncached
[ 72140 ]  Anzahl gültiger Files/Webstreams: 50
[ 72140 ]  build playlist from SD-card finished: 385 ms

#define CACHED_PLAYLIST_ENABLE aktiv & playlistcache-Datei vorhanden::

[ 120845 ]  Playlist-Generierung: cached
[ 120965 ]  Freier Speicher: 79908
[ 120982 ]  Anzahl gültiger Files/Webstreams: 50
[ 120982 ]  build playlist from SD-card finished: 207 ms

CACHED_PLAYLIST_ENABLE auskommentiert, also komplett ohne Cache:

[ 41361 ]  Playlist-Generierung: uncached
[ 41410 ]  Anzahl gültiger Files/Webstreams: 50
[ 41410 ]  build playlist from SD-card finished: 57 ms

Also ja, tatsächlich deutlich besser ohne :slight_smile: .
Das define CACHED_PLAYLIST_ENABLE könnte man in diesem Zuge auch gleich ganz raus nehmen. Ich kann mir morgen auch nochmal die originalen Zeiten zum Vergleich anschauen. Das sollte ganz spannend sein.
Gute Nacht zusammen!

2 „Gefällt mir“

Hattest du bei den Messungen Musik abgespielt? Weil die Unterschiede zu tueddy scheinen doch sehr hoch zu sein (oder nutzt du SPI). Würde mich nur interessieren, da du so große Untershiede hast. Kann natürlich auch an der SD-Karte selbst liegen :upside_down_face:.

Ohne der Cache sind die Werte aber deutlich besser (wahrscheinlich weil es einfach fixer geht den FAT32 Directory table auszulesen als die Linked List der Datei). Sehr überzeugend :laughing:. Muss nur die Zeit finden meine Playlist Branch nochmal anzugreifen… RL funkt da gerade leider ein wenig dazwischen…

Messungen sind jeweils zum Start des Abspielens implementiert. Ich verwende das normale Kit ohne SPI.

Aber ich finde die Unterschiede gar nicht so gravierend. Dass das erstellen der Playlist und speichern länger dauert wenn es mehr Dateien sind und die SD nur langsam schreibt passt.
Beim lesen des Chache-Files bei ca. doppelter Anzahl an Einträgen auch etwa doppelt so lange wirkt auch plausibel.
Und beim lesen der Table ohne das Cache file ebenfalls etwa doppelt so lange bei doppelt so vielen Einträgen…