Autor |
Beitrag |
Gausi
![Gausi hat insgesamt 77.7% On-Topic und 22.3% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![Gausi hat insgesamt 77.7% On-Topic und 22.3% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![Gausi hat insgesamt 77.7% On-Topic und 22.3% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![Gausi hat insgesamt 77.7% On-Topic und 22.3% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![Gausi hat insgesamt 77.7% On-Topic und 22.3% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![Gausi hat insgesamt 77.7% On-Topic und 22.3% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![Gausi hat insgesamt 77.7% On-Topic und 22.3% Off-Topic Beiträge geschrieben. offtopic star](./graphics/ranks/star_offtopic_full.png)
Beiträge: 8535
Erhaltene Danke: 473
Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
|
Verfasst: So 04.10.20 12:11
Ich hätte da mal wieder eine kleine konzeptionelle Frage.
Folgende Situation: Ich habe für meine Audio-Bibliothek den Code ein wenig geändert und verwende dort nun ein Factory-Pattern.
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| function TAudioFileFactory.CreateAudioFile(aFilename: UnicodeString): TBaseAudioFile; var ext: UnicodeString; aClass: TBaseAudioFileClass; begin ext := AnsiLowercase(ExtractFileExt(aFileName)); aClass := GetClass(ext); if assigned(aClass) then Result := aClass.Create else Result := Nil; end; |
Soweit so gut. Ich kann damit dann im Anwendungs-Code ein TBaseAudioFile (abstrakte Basisklasse) deklarieren, und mir über die Factory eine passende Instanz einer konkreten Klasse erzeugen lassen, das ich dann weiter verwenden kann.
Die Methode GetClass verwendet ein TDictionary, alternativ für ältere Delphi-Versionen eine einfache TObjectList, der ich jedoch eine "Selbstanordnung" spendiert habe, d.h. bei Lese-Zugriff auf einen Eintrag wird dieser nach oben geschoben, so dass wiederholte Zugriffe auf dieses Element schneller gehen.
Zur Erzeugung der Factory-Klasse nutze ich das Konzept über eine global bekannte Funktion und einer lokalen Variable (das ist wohl auch ziemlich standard so)
Delphi-Quelltext 1: 2: 3: 4: 5: 6:
| function AudioFileFactory: TAudioFileFactory; begin if not Assigned(fLocalAudioFileFactory) then fLocalAudioFileFactory := TAudioFileFactory.Create; Result := fLocalAudioFileFactory; end; |
Problem ist: Das brauche ich in verschiedenen Threads. Meine selbstanordnende Liste ist völlig klar nicht thread-safe. Zu TDictionary gibt es im Netz auch widersprüchliche Aussagen. Wenn ich mir den TDictionary-Code anschaue, scheint der bei reinem Lesezugriff(!) thread-safe zu sein - aber es gibt natürlich keine Garantie, dass das so bleibt.
Frage, wie sichere ich das am besten ab?
- Erstelle ich mir für jeden Thread eine eigene Factory? Dann müssten meine Objekte (oder zumindest die Factory-relevanten Methoden) den Thread kennen, in dem sie laufen. Ich bräuchte dann ca. ein halbes Dutzend Factories, wobei praktisch immer höchstens eine arbeitet. Halte ich für nicht so elegant.
- Soll ich darauf vertrauen, dass TDictionary thread-safe ist (meine Alternative mit der ObjectList würde ich dann um-implementieren)? Augen zu und durch, wird schon nicht schief gehen?
- Oder packe ich die GetClass-Aufrufe in eine CriticalSection? Aber wäre das für SingleThread-Anwendungen nicht eine Verschwendung von Ressourcen? GetClass kann dabei so 10-100x pro Sekunde aufgerufen werden, insgesamt mehrere 10.000mal in einer Anwendung.
Kleine Testläufe mit sowas
Delphi-Quelltext 1: 2: 3: 4: 5: 6:
| function AddOneCS(value: Integer): Integer; begin EnterCriticalSection(CS); result := value + 1; LeaveCriticalSection(CS); end; | deuten an, dass bei 1.000.000 Durchläufen die Zeit messbar anders wird (ca. 920 bzw. 950ms, inkl. Ausgabe in eine Memo alle 1000 Schritte). Bei den zu erwartenden Aufrufzahlen in meinem Fall wäre das aber vernachlässigbar. Aber irgendwie finde ich das auch nicht ideal.
Wie handhabt ihr sowas? Auf hard-codierte if-then-else-Konstrukte zur Bestimmung der richtigen Klasse habe ich nämlich auch nicht so Lust...
_________________ We are, we were and will not be.
|
|
jfheins
![jfheins hat insgesamt 76.6% On-Topic und 23.4% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![jfheins hat insgesamt 76.6% On-Topic und 23.4% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![jfheins hat insgesamt 76.6% On-Topic und 23.4% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![jfheins hat insgesamt 76.6% On-Topic und 23.4% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![jfheins hat insgesamt 76.6% On-Topic und 23.4% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![jfheins hat insgesamt 76.6% On-Topic und 23.4% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![jfheins hat insgesamt 76.6% On-Topic und 23.4% Off-Topic Beiträge geschrieben. offtopic star](./graphics/ranks/star_offtopic_full.png)
Beiträge: 918
Erhaltene Danke: 158
Win 10
VS 2013, VS2015
|
Verfasst: Mo 05.10.20 08:35
Moin
Du musst natürlich bereits function AudioFileFactory: TAudioFileFactory; um eine critical section ergänzen, damit du auch wirklich nur eine Instanz bekommt. (Potenzielles Speicherleck). Ich würde erstmal das probieren und dann gucken ob es noch benutzbar ist.
Zitat: | Aber wäre das für SingleThread-Anwendungen nicht eine Verschwendung von Ressourcen? |
Bei single thread sollte die CS nur minimal overhead haben, da der Fall "A möchte rein, B ist schon drin" ja nicht auftreten sollte. Und der zieht ja die Performance runter, wenn Threads wirklich warten müssen.
Für diesen Beitrag haben gedankt: Gausi
|
|
jaenicke
![jaenicke hat insgesamt 87.9% On-Topic und 12.1% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![jaenicke hat insgesamt 87.9% On-Topic und 12.1% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![jaenicke hat insgesamt 87.9% On-Topic und 12.1% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![jaenicke hat insgesamt 87.9% On-Topic und 12.1% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![jaenicke hat insgesamt 87.9% On-Topic und 12.1% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![jaenicke hat insgesamt 87.9% On-Topic und 12.1% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![jaenicke hat insgesamt 87.9% On-Topic und 12.1% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png)
Beiträge: 19276
Erhaltene Danke: 1741
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Mo 05.10.20 18:41
Nimm keine Critical Section, zumindest für neuere Delphiversionen. Das ist für deinen Zweck performancemäßig absoluter Overkill.
TMonitor.Enter / TMonitor.Exit ist in aktuellen Delphiversionen deutlich schneller nach einer Optimierung ca. in den ersten XE Versionen. Da laufen zuerst ein paar Spins und wenn man wie hier nur sehr kurze Verweilzeiten in der geschützten Sektion hat, ist das sehr viel schneller als eine Critical Section. Erst nach den Spins werden echte Locks verwendet.
Aber für diesen Fall:
Ja, ein TDictionary ist in der aktuellen Implementierung (!) threadsicher solange man nur lesend darauf zugreift. Da dies aber nicht in der Dokumentation steht, könnte eine zukünftige Änderung dies theoretisch ändern. (Ich halte es allerdings für unwahrscheinlich, dass das passiert.)
Bei nur wenigen Einträgen in der Liste vermute ich allerdings, dass ein TDictionary kaum einen Performancevorteil gegenüber einer Liste oder auch einem einfachen Array hat.
Gausi hat folgendes geschrieben : | Erstelle ich mir für jeden Thread eine eigene Factory? |
Es würde reichen eine threadvar zu verwenden, die ggf. den Inhalt aus einer globalen Variable bekommt, wenn sie noch nicht initialisiert ist. Der Rest der Factory kann dann auch nur einmal existieren.
Für diesen Beitrag haben gedankt: Gausi, Sinspin
|
|
Gausi ![Threadstarter Threadstarter](./graphics/viewtopic/threadstarter.png)
![Gausi hat insgesamt 77.7% On-Topic und 22.3% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![Gausi hat insgesamt 77.7% On-Topic und 22.3% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![Gausi hat insgesamt 77.7% On-Topic und 22.3% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![Gausi hat insgesamt 77.7% On-Topic und 22.3% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![Gausi hat insgesamt 77.7% On-Topic und 22.3% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![Gausi hat insgesamt 77.7% On-Topic und 22.3% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![Gausi hat insgesamt 77.7% On-Topic und 22.3% Off-Topic Beiträge geschrieben. offtopic star](./graphics/ranks/star_offtopic_full.png)
Beiträge: 8535
Erhaltene Danke: 473
Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
|
Verfasst: Mo 05.10.20 20:48
jfheins hat folgendes geschrieben : | Du musst natürlich bereits function AudioFileFactory: TAudioFileFactory; um eine critical section ergänzen, damit du auch wirklich nur eine Instanz bekommt. (Potenzielles Speicherleck). |
Jep, das ist mir auch klar. Für den Fall würde ich dann aber einfach eine globale Factory-Variable nehmen, die im Initialization-Abschnitt erstellt wird. Da sollten dann beim Aufbau der Factory keine Threads reinpfuschen können.
jaenicke hat folgendes geschrieben : | Nimm keine Critical Section, zumindest für neuere Delphiversionen. Das ist für deinen Zweck performancemäßig absoluter Overkill.
TMonitor.Enter / TMonitor.Exit ist in aktuellen Delphiversionen deutlich schneller nach einer Optimierung ca. in den ersten XE Versionen. |
Bei meinen Vorab-Recherchen habe ich zunächst diesen Blog gefunden, und darin dann jetzt (etwas versteckt) den Link zu diesem Update. Das klingt für mich so, als wären die beiden Varianten in etwa äquivalent. Kannst du das ggf. näher erläutern, oder hast du noch mehr Quellen dazu? Ich gebe zu, dass ich die beiden Artikel noch nicht ganz durchblicke ...
Das Thema "Fairness" kann ich bei mir wohl quasi vernachlässigen. In der Regel läuft nur ein Thread, und ggf. möchte ein zweiter auch mal kurz eine einzelne Aktion durchführen - aber das war es dann.
Zum Dictionary generell: Ja, das ist in dem Fall vermutlich wirklich Overkill. Ich könnte mir auch gut vorstellen, dass meine selbstanordnende TOjectList letztlich schneller ist (und vermutlich das Optimum) - der übliche Usecase ist, dass sehr oft auf den gleichen Eintrag zugegriffen wird, und nur ab und zu auf andere. Aber da brauche ich dann auf jeden Fall Thread-Sicherheit, da in dem Fall ein Lesezugriff die Liste verändert. Die Liste hat ca. 20 Einträge, und vermutlich wird zu 99% auf höchstens 5 davon zugegriffen.
Auf der anderen Seite ist das alles egal, weil das nicht der Flaschenhals ist: Danach wird eine Datei geöffnet, und teilweise gelesen und geparsed ...
_________________ We are, we were and will not be.
|
|
jaenicke
![jaenicke hat insgesamt 87.9% On-Topic und 12.1% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![jaenicke hat insgesamt 87.9% On-Topic und 12.1% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![jaenicke hat insgesamt 87.9% On-Topic und 12.1% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![jaenicke hat insgesamt 87.9% On-Topic und 12.1% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![jaenicke hat insgesamt 87.9% On-Topic und 12.1% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![jaenicke hat insgesamt 87.9% On-Topic und 12.1% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png) ![jaenicke hat insgesamt 87.9% On-Topic und 12.1% Off-Topic Beiträge geschrieben. ontopic star](./graphics/ranks/star_ontopic_full.png)
Beiträge: 19276
Erhaltene Danke: 1741
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Di 06.10.20 06:50
Gausi hat folgendes geschrieben : | Das klingt für mich so, als wären die beiden Varianten in etwa äquivalent. |
Das stimmt für viele Fälle auch. In wenigen echten Programmen hatte ich allerdings teilweise deutliche Vorteile bei TMonitor. Da ich mich nicht damit beschäftigt habe, weiß ich nicht genau warum. Aber an einer Stelle lag ich oben falsch: Die Critical Section nutzt genauso Spin-Locks.
TMonitor sollte aber zumindest in so gut wie allen Fällen nicht spürbar langsamer sein und ist natürlich deutlich einfacher zu benutzen.
Für diesen Beitrag haben gedankt: Gausi
|
|
|