Zur Frage der Mengen

Alice: Warum ist dieser Ort ein sehr seltsamer Ort?
Dodo: Und weil alle anderen Orte nicht sehr seltsam sind. Es muss mindestens einen SEHR seltsamen Ort geben.



Schauen wir uns also den Text der Vorlagenklasse BitSet an, um ihn an die Anforderungen von MK anzupassen. Die Hauptbereiche der Optimierung wurden zuvor definiert. Sie können natürlich Ihre eigene Klasse von Grund auf neu schreiben, aber lassen Sie uns die Gelegenheit, gute Lösungen kennenzulernen, nicht vernachlässigen, da die STL-Bibliothek (nicht zu verwechseln mit spl) seit langem bekannt ist und überall verwendet wird. Zuerst müssen Sie den Quellcode finden. Nach einer kurzen Reise im Internet habe ich gerade das Verzeichnis mit meinem MinGW geöffnet und nach der erforderlichen Datei gesucht, die ich weiter diskutieren möchte.

Zu Beginn sehen wir etwas mehr als eine Seite mit Copyright und dem Text der Lizenz sowie eine Vielzahl von Warnungen - nun, Sie können dies überspringen, dies ist für Anwälte. Als nächstes kommen die Includes und ich danke den Autoren - jeder wird von einem Kommentar darüber begleitet, was wir aus jeder Datei erhalten möchten - jeder würde das tun, die Arbeit des Indiana Jones- Forschers wäre einfacher. Als nächstes kommen die Definitionen, ich bin kein Fan von Code-Sauberkeit und werde nicht streiten - lassen Sie sie sein und sehen Sie sofort die Definition der Anzahl der Bits in der Speichereinheit, die eine lange Zeit ohne Vorzeichen ist. Ich setze den rutschigen Pfad von Defines fort und suche nach meiner eigenen Speichereinheitsdefinition, die uint8_fast entspricht, obwohl ich verstehe, dass dies eindeutig nicht ausreichen wird. Übrigens, ich danke den Autoren sofort noch einmal für den Stil - am Ende des Moduls räumen sie nach sich selbst auf und zerstören interne Definitionen - ich hatte nicht einmal gehofft, dass ich so etwas in der modernen Produktion sehen würde.

Und hier beginnen leichte Missverständnisse - zunächst definieren sie die Template-Hilfsstruktur struct _Base_bitset (warum genau die Struktur und nicht die Klasse, da sie austauschbar sind), in der sie den Typ für die Datenspeicherung bestimmen und dieser erneut direkt spezifiziert - wir ändern ihn in unseren eigenen. Als nächstes werden zwei Instanzen dieser Hilfsstruktur bestimmt - für eine Speichereinheit (das ist verständlich, um die Effizienz des Codes zu erhöhen) und für ein entartetes Beispiel ohne Speicherung (wahrscheinlich zur Fehlerbehandlung), bis wir diesen Code berühren.

Als nächstes finden wir das Hauptklassenklassen-Bitset (jetzt die Klasse), das aus der Vorlagenstruktur abgeleitet ist (ja, in C ist es möglich, es ist nicht klar, warum dies zu tun ist, aber es ist möglich), in dem der Speichertyp erneut bestimmt und wieder in unseren geändert wird. Aber hier habe ich mich gefragt - warum der Typ nicht von der übergeordneten Struktur geerbt wurde und war erstaunt (ja mit Erstaunen, Vorlagenklassen - das mache ich im Alltag nicht), dass sich die Vererbung von Vorlagenklassen etwas von den normalen unterscheidet. Das heißt, die Vererbung ist genau die gleiche und alle Entitäten des Elternteils sind in der Tochterklasse verfügbar (ich hoffe, niemand wird mich für eine geschlechtsspezifische Chauvinistin halten), aber sie müssen mit einem vollständigen Namen angegeben werden. Nun, dies kann immer noch verstanden werden, andernfalls wird der Compiler nicht erraten, welche der instanziierten Optionen angewendet werden soll. Wenn Sie jedoch die in der Klasse definierten Typen verwenden, müssen Sie immer noch das Schlüsselwort typename hinzufügen. Dies ist keine offensichtliche Lösung. Ich war besonders zufrieden mit der Diagnose des Compilers, was bedeutet, dass "vielleicht hatten Sie einen Typ aus der Elternklasse" - ja, danke, Kapitän, das habe ich gemeint, ich bin froh, dass wir uns verstehen, aber Einfacher geht es mir nicht. Eine solche Diagnose findet jedoch in der alten Version von GCC statt. In der aktuellen Nachricht wird klarer: "Vielleicht haben Sie das Schlüsselwort typename vergessen." Das Verständnis der Diagnosemeldungen des Compilers für Vorlagenklassen ist im Allgemeinen ein Thema für ein separates Lied, und dieses Lied ist keineswegs erfreulich.

Wenn wir das Konstrukt also nicht ständig in der untergeordneten Klasse verwenden möchten

std::_Base_bitset<Nb>::_WordT 

(und wir wollen nicht, neg?) Dann haben wir drei Möglichkeiten

1) trivial

 #define _WordT typename _Base_bitset<_Nb>::_WordT 

2) Offensichtliche Typdefinition auf globaler Ebene und mir hat es besser gefallen

3) für Fans von fremden

 typedef typename _Base_bitset<_Nb>::_WordT _WordT; 

(anhaltendes Gefühl einer prächtigen Chromkrücke). Persönlich bin ich in allen drei Fällen mit der direkten Angabe der übergeordneten Klasse nicht zufrieden, da die Implementierung die Vererbungskette nicht berücksichtigen sollte, aber vielleicht verstehe ich etwas nicht.

Es gibt auch eine Methode 4)

  using _WordT = std::_Base_bitset<Nb>::_WordT; 

aber in meiner Version des Compilers funktioniert es nicht, also ist es nicht.

Anmerkung zu den Rändern (PNP) - In dem wunderbaren Buch „C ++ für echte Programmierer“ (ich habe es damals versehentlich heruntergeladen und festgestellt, dass es in Echtzeitsystemen C ++ gewidmet ist) heißt es über die Sprache „Seine Eleganz ist keineswegs in Einfachheit (die Wörter C ++ und Einfachheit schneiden das Ohr mit seinem offensichtlichen Widerspruch), aber in seinen möglichen Fähigkeiten. Für jedes hässliche Problem gibt es eine Art kluges Idiom, eine elegante Sprachschwäche, dank der das Problem direkt vor unseren Augen schmilzt. “ Da das Buch wirklich wunderbar ist, bedeutet dies, dass das obige Zitat korrekt ist (ich verstehe, dass es hier eine gewisse logische Ausdehnung gibt), aber ich persönlich sehe das Problem leider, aber es gibt eine Belastung mit einer klugen Sprache.

Trotz des verbleibenden Gefühls der leichten Verwirrung ist das Problem gelöst und Sie können das Ergebnis genießen - aber es war nicht da. Beim Ändern der Größe des Testsatzes von 15 auf 5 ist ein Kompilierungsfehler völlig unerwartet aufgetreten. Nein, alles ist klar, eine unveränderte Instanziierung mit Vorlagenparameter 1 hat funktioniert, aber von außen sieht es sehr seltsam aus - wir ändern die Konstante und das Programm stoppt die Kompilierung.

Sie können diese und eine andere Implementierung natürlich ändern, aber ein solcher Ansatz scheint eine grobe Verletzung des DRY-Prinzips zu sein. Es gibt mögliche Lösungen, und es gibt mehr als eine davon: 1) die offensichtliche - wieder die Definition und 2) die triviale - wieder die Typdefinition auf globaler Ebene, aber in diesem Fall wird sie aus dem Modul herauskommen, was jedoch in der betrachteten Implementierung aus der Grundstruktur herausragt , aber auch das Qualifikationsmerkmal muss nicht geschrieben werden.

Daher neige ich zu Option 3) der Basisklasse zur Bestimmung des Typs und der Vererbung aller Instanzen davon. Darüber hinaus sind die Entitäten der übergeordneten Klasse wieder geschützt, damit sie nicht hervorstehen. Dann finde ich eine lustige Eigenschaft, wahrscheinlich sollte C ++ für seine Flexibilität gelobt werden - für die Variante ohne Speicher wird der Typ nicht benötigt, und die Sprache ermöglicht die Verwendung der Implementierung ohne Vererbung, obwohl dies in diesem speziellen Fall nicht besonders notwendig ist. Sofort entdecke ich einen weiteren Nachteil der Bibliothek - in allen drei Varianten werden die Operationen zur Berechnung der Speichereinheitsnummer jedes Mal eingestellt

 static size_t _S_whichword 

und die darin enthaltenen Bitmasken _S_maskbit und sie sind völlig identisch - wir verschieben sie auch in die Basisklasse. In diesem Fall wird ein "toter" Code erkannt - die _S_whichbyte-Methode, ich weiß nicht einmal, was ich tun soll - einerseits erfordern gute Tonregeln das Entfernen, andererseits - im Fall einer Vorlage hat dies keinen Einfluss auf den resultierenden Code. Ich werde die Regel "etwas nicht verstehen - nicht berühren" anwenden und diese Methode verlassen.

Grundsätzlich sind die Änderungen hinsichtlich der Art des Speichers abgeschlossen und Sie können mit dem Testen beginnen. Und sofort stelle ich einen Mangel an Implementierung fest - für die MSP430-Architektur werden aus irgendeinem Grund zwei Byte-Wörter anstelle von Bytes zugewiesen. Natürlich keine doppelten Wörter wie zuvor, aber wir kämpfen immer noch um den minimalen (in jeder Hinsicht) Code. Es stellt sich heraus, dass der Compiler sicher ist, dass in dieser Architektur der Typ uint_fast8_t die Größe 2 hat, obwohl das Befehlssystem Byte-Operationen hat und der Compiler selbst diese vollständig verwendet. Die Idee, diesen Typ zu verwenden, ist gefährdet und Sie müssen den Datentyp uint8_t direkt festlegen. Wenn es eine Architektur gibt, in der die Arbeit mit Bytes nicht erfolgreich ist und die Größe des Typs uint_fast8_t von 1 abweicht (keine der im Compiler verfügbaren), muss sie unter der Geschwindigkeit aller anderen leiden.

Das Testen der korrigierten Version zeigt die korrekte Funktionsweise auf verschiedenen Architekturen und mit unterschiedlichen Parametern, aber es gibt immer noch eine Frage bei der Berechnung von Bitmasken für MK ohne entwickelte Verschiebungen, in unserem Fall MSP430 und AVR. Im Prinzip können Sie das Lesen der Bitmaske aus dem Array einfach zu einer Methode für alle Fälle machen, unabhängig von der MK-Architektur. Die Lösung funktioniert ziemlich gut, in entwickelten Architekturen mit Indizierung ist alles in Ordnung, aber es wird immer noch einen Zeitverlust im Vergleich zu schnellen Schichten geben, aber ich möchte nicht mit den Worten „Die Klasse wird schneller sein, sagte er, wir optimieren sagte er. "

Daher benötigen wir eine andere Implementierung für unsere beiden schwachen Architekturen, die sich von allen anderen in der Größe des Typs uint_fast16_t unterscheiden - es ist 2 gegenüber 4 oder 8 für 64-Bit-Versionen. Die bedingte Kompilierung wird uns nicht helfen, aber das Setzen der Bedingung in der Hoffnung auf Optimierung ist nicht der Weg der Samurai, die Muster bleiben. Ich versuche, eine Vorlagenmethode für die Klasse zu erstellen, aber eine teilweise Spezialisierung ist dafür nicht verfügbar. Es stellt sich heraus, dass dies der Fall sein sollte, und ich finde sogar einen Artikel, in dem angegeben ist, warum dies eine gute Lösung ist. Ehrlich gesagt habe ich immer noch nicht verstanden, warum diese Entscheidung gut ist, höchstwahrscheinlich aufgrund religiöser Erwägungen. Trotzdem wurden wir nicht gefragt, und was noch zu tun ist - Sie können eine benutzerfreundliche Vorlagenfunktion erstellen (es stellte sich heraus, dass dies unmöglich ist, und aus dem gleichen Grund - eine teilweise Spezialisierung ist verboten), es gibt eine andere Lösung, es sieht lustig aus - eine teilweise Spezialisierung der Methode außerhalb des Klassenkörpers. Sie können sogar eine separate Vorlagenfunktion erstellen und über die Klassenmethoden darauf zugreifen. Ich weiß nicht, welche Methode korrekter ist. Ich habe diese ausgewählt. Ja, sie hat keinen Zugriff auf die internen Einheiten der Klasse, aber in diesem speziellen Fall ist es nicht erforderlich, was bei Bedarf zu tun ist. Ich weiß noch nicht: "Wir werden Probleme lösen, sobald sie auftreten."

Jetzt haben wir alles, was wir brauchen. Wir sammeln die getesteten Fragmente zusammen und erhalten den Code, der das anfängliche Optimierungsproblem löst. Gleichzeitig haben wir den Code kompakter gemacht (was verständlicher bedeutet, obwohl letzteres umstritten ist), zahlreiche Wiederholungen entfernt und überflüssige Entitäten beseitigt, die herausragen. Das einzige, was nicht festgelegt ist, ist eine Mischung aus Strukturen und Klassen, aber hier verstehe ich die Gründe für eine solche Implementierung nicht, daher werde ich diesen Teil nur für den Fall nicht berühren.

Ein zweiter Beitrag wird der Implementierung der zweiten Version des Sets gewidmet sein, und ohne das hat etwas viel geklappt.

Source: https://habr.com/ru/post/de450176/


All Articles