Wear Leveling ist in jedem anständigen SD/eMMC verfügbar, ganz einfach
Definition. SSD (obwohl nicht die kleinste mit interliv
anständig) haben einen erheblichen Wechselkursvorteil, aber
Wenn Sie über USB 2.0 angeschlossen sind, verlieren Sie das meiste davon
Vorteile. Normale Datenbank mit permanentem Schreibzugriff auf die Festplatte
erfordert normalen Speicher und einen normalen Prozessor. Und das Normale
Der Prozessor ist SATA oder PCI-E.
LightElf (24.02.2021 22:19, Aufrufe: 443) antwortete auf Evgeny_CD bis (SD, MMC) != SSD. SSD ist Wear Leveling und vieles mehr.
Hält tatsächlich mindestens 3.000 vollständige Überschreibungen der gesamten Festplatte
(das sind die günstigsten QLCs), 10.000 oder mehr bei älteren Modellen. So
Was wäre, wenn Sie eine normale Datenbank mit ständigem Schreiben auf die Festplatte und dann auf die SSD erstellen würden?
ganz besser.
Wear Leveling ist in jedem anständigen SD/eMMC verfügbar
Definition. SSD (obwohl nicht die kleinste mit interliv
anständig) haben einen erheblichen Wechselkursvorteil, aber
Wenn Sie über USB 2.0 angeschlossen sind, verlieren Sie das meiste davon
Vorteile. Normale Datenbank mit permanentem Schreibzugriff auf die Festplatte
erfordert normalen Speicher und einen normalen Prozessor. Und das Normale
Der Prozessor ist SATA oder PCI-E.
Tun Sie nicht das, was für mich am besten ist, lassen Sie es einfach gut
Antwort
-
- Wenn wir laut Pass eMMC -40 nehmen, wird alles sehr
Schade um den Preis und die Verfügbarkeit. mSATA und einfach das gute alte 2,5″
SATA für kleine Kapazitäten in der -40-Variante sind nahezu Stück für Stück erhältlich
und kosten relativ vernünftiges Geld. Evgeny_CD (423 Zeichen, 24.02.2021 22:25)- Wo sind Sie auf 2,5″ SATA bei -40 gestoßen? — kaf1 (25.
02.2021 06:25)
- Lieferanten liefern auf Bestellung. Adata, Ttanscend. — Evgeny_CD (25.02.2021 10:33)
- Haben Sie Datenblätter? Adata, Ttanscend-Discs nicht. — kaf1 (25.02.2021 10:36)
- Achepyatka — Evgeny_CD (25.02.2021 10:42 — 10:55, Link, Link)
- Das ist für einen Desktop-PC und das ist eine SSD, Sie haben von normalen Festplatten gesprochen. A
Wo ist -40? — kaf1 (25.02.2021 10:45 — 10:47)- Sehen Sie sich die Temperaturbereichsoptionen an und seien Sie nicht langweilig. — Evgeny_CD (25.02.2021 10:46)
- Sie geben mindestens einen Link an.
— kaf1 (25.02.2021 10:49)
- Du hast mich überrascht Evgeny_CD (60 Zeichen, 25.02.2021 11:02, Link)
- Dies ist eine SSD. Sie haben über gewöhnliche Festplatten geschrieben. — kaf1 (25.02.2021 11:08)
- Ich habe überall über SSD geschrieben. — Evgeny_CD (25.02.2021 11:08)
- Und wo ist das „gute alte 2,5““ – kaf1 (25.02.2021 11:11)
- Bist du ein Idiot? Vorderseite 2,5-Zoll-SSD, SATA3, 3D TLC, PE: 3K, Wide
Temp — Evgeny_CD (25.02.2021 11:13)- Ich bin im Allgemeinen ein Idiot. Wenn es eine Größe gibt, dann ist es die Mechanik.
Ich habe verstanden, dass du
Ich meine normale Festplatten. — kaf1 (25.02.2021 11:16)- Disc 2,5″ impliziert ein Standarddesign. Mechanik
dort, oder es ist aus einem Stück Vibranium gemacht, was auch immer. Ich dachte es
sehr bekannt. Sei nicht böse. Als sie SSD-Laufwerke herstellten, haben sie es offensichtlich getan
baulich und elektrisch austauschbar gemacht
mechanische Scheiben. — Evgeny_CD (25.02.2021 11:23)
- Disc 2,5″ impliziert ein Standarddesign. Mechanik
- Ich bin im Allgemeinen ein Idiot. Wenn es eine Größe gibt, dann ist es die Mechanik.
- Bist du ein Idiot? Vorderseite 2,5-Zoll-SSD, SATA3, 3D TLC, PE: 3K, Wide
- Und wo ist das „gute alte 2,5““ – kaf1 (25.02.2021 11:11)
- Ich habe überall über SSD geschrieben. — Evgeny_CD (25.02.2021 11:08)
- Dies ist eine SSD. Sie haben über gewöhnliche Festplatten geschrieben. — kaf1 (25.02.2021 11:08)
- Du hast mich überrascht Evgeny_CD (60 Zeichen, 25.02.2021 11:02, Link)
- Sie geben mindestens einen Link an.
- Sehen Sie sich die Temperaturbereichsoptionen an und seien Sie nicht langweilig. — Evgeny_CD (25.02.2021 10:46)
- Das ist für einen Desktop-PC und das ist eine SSD, Sie haben von normalen Festplatten gesprochen. A
- Achepyatka — Evgeny_CD (25.02.2021 10:42 — 10:55, Link, Link)
- Haben Sie Datenblätter? Adata, Ttanscend-Discs nicht. — kaf1 (25.02.2021 10:36)
- Lieferanten liefern auf Bestellung. Adata, Ttanscend. — Evgeny_CD (25.02.2021 10:33)
- 32GB ISSI ist auf Lager, 30 Euro im Einzelhandel. Durch Geschwindigkeit vervielfachen
Funktioniert mit jedem USB2.0-Adapter. LightElf (86 Zeichen, 24.02.2021 22:39, Link, Link)- 64G ISSI SDINBDG4-64G-XI1 ebenda 10 — 60 $, und das ist es tatsächlich
bricht meine „geniale“ Idee in USB <-> SATA ab – Evgeny_CD (24.02.2021 23:08)
- 32G hat ein schickes BGA 1.0-Paket! Wow! — Evgeny_CD (24.02.2021 22:58)
- Warum habe ich das zum x-ten Mal? „Die Seite ist vorübergehend
nicht verfügbar. Bitte wenden Sie sich umgehend an Ihre örtliche Niederlassung
Hilfe…» Neulich gab es das gleiche Kanu. — Codavr (24.02.2021 22:50)- Vorübergehende Netzwerkbehinderung. Sei geduldig, neue Welt
kommt… — Evgeny_CD (24.02.2021 22:57)- Bitte stehen Sie bereit, schöne neue Welt wird geladen… — SciFi (24.02.2021 22:59)
- Ups. Es sieht so aus, als ob der Proxy einen Fehler macht. — Codavr (24.02.2021 22:56)
- Vorübergehende Netzwerkbehinderung. Sei geduldig, neue Welt
- Es gibt also Fortschritte, danke! Interessant! Aber es gibt Nuancen.
Willkürlich
Schreiben (IOPS) – 1641. Bei Sektoren von 512 Byte – 820 KByte/Sek. Wenn
4k — 6,5 MB/Sek. Irgendwie kann man es nicht „Geschwindigkeit mit USB“ nennen
2,0 ist im Vergleich sogar unanständig.“ Ja, und die Anzahl der Vollzyklen
Ich habe keine Umschreibung gefunden. — Evgeny_CD (24.02.2021 22:47)- JJJJ. Intel hat die Produktion primitiver Optans eingestellt. Obwohl
Es ist klar, warum – sie gingen nicht zu den Massen. Da ist alles zufällig drin
Okay. — Mahagam (24.02.2021 23:44)- Verbleibende MCU mit PCIe x4, und wir freuen uns 🙂 — Evgeny_CD (24.02.2021 23:47)
- Woher bekommt diese MCU Daten, um sogar PCIe x1 zu booten? 🙂 A
also — es gibt einen günstigen MT7628KN, mit eingebautem Speicher und PCI-E — LightElf (25.02.2021 00:02, Link, Link)
- Umgebungstemperaturbereich -20 bis 55 °C – unsportlich. Chip natürlich
interessant. — Evgeny_CD (25.02.2021 00:09)
- Umgebungstemperaturbereich -20 bis 55 °C – unsportlich. Chip natürlich
- Woher bekommt diese MCU Daten, um sogar PCIe x1 zu booten? 🙂 A
- Verbleibende MCU mit PCIe x4, und wir freuen uns 🙂 — Evgeny_CD (24.02.2021 23:47)
- Übrigens, wie viele IOPS gibt es über USB? — LightElf (24.02.2021 23:09)
- Und so schreibt man ein Austauschprotokoll 🙂 — Evgeny_CD (24.02.2021 23:10)
- SDINBDG4-64G-XI1, 64G, schon viel mehr Spaß – 15.000 zufällige IOPS pro
Aufnahme, 60 MB/Sek., ja, USB 2.0 dürfte scheiße sein. Aber! Ich möchte
Informieren Sie sich über die mengenabhängige Verschlechterung dieses Parameters
Zyklen… — Evgeny_CD (24.02.2021 22:56)- Möchten Sie mehr über die Verschlechterung der SSD erfahren? 🙂 Aber solche microSD
jetzt (nicht wie gerade!) LightElf (1 Zeichen, 24.02.2021 23:08, Bild)
- Und das ist wie ein chinesisches „100-MHz“-Oszilloskop 🙂 Es gibt keine IOPS. Aber ja,
Der Fortschritt kommt! — Evgeny_CD (24.02.2021 23:10)
- Und das ist wie ein chinesisches „100-MHz“-Oszilloskop 🙂 Es gibt keine IOPS. Aber ja,
- Möchten Sie mehr über die Verschlechterung der SSD erfahren? 🙂 Aber solche microSD
- „Was für eine Schande!“, SunDisk verspricht 15.000 Schreib-IOPS und 25.000 Schreib-IOPS
Lektüre. — LightElf (24.02.2021 22:55)
- JJJJ. Intel hat die Produktion primitiver Optans eingestellt. Obwohl
- 64G ISSI SDINBDG4-64G-XI1 ebenda 10 — 60 $, und das ist es tatsächlich
- Wo sind Sie auf 2,5″ SATA bei -40 gestoßen? — kaf1 (25.
- Wenn wir laut Pass eMMC -40 nehmen, wird alles sehr
kombinieren Geschwindigkeit und hohes Niveau. Bericht von Yandex / Habr
Was beeinflusst die Geschwindigkeit von Programmen in C++ und wie erreicht man dies mit einem hohen Codeniveau? Der Hauptentwickler der CatBoost-Bibliothek, Evgeny Petrov, beantwortete diese Fragen anhand von Beispielen und Illustrationen aus der Erfahrung mit CatBoost für x86_64.
Videobericht
— Hallo alle. Ich führe eine CPU-Optimierung für die CatBoost-Bibliothek für maschinelles Lernen durch. Der Hauptteil unserer Bibliothek ist in C++ geschrieben. Heute erzähle ich Ihnen, mit welchen einfachen Mitteln wir Geschwindigkeit erreichen.
Die Rechengeschwindigkeit besteht aus zwei Teilen. Der erste Teil ist der Algorithmus. Wenn wir bei der Wahl des Algorithmus einen Fehler machen, können wir ihn später nicht schnell zum Laufen bringen. Im zweiten Teil geht es darum, wie unser Algorithmus hinsichtlich Leistung und Durchsatz für das Computersystem optimiert wird, über das wir verfügen.
Datenaustausch und Berechnungen müssen aufgrund des großen Geschwindigkeitsunterschieds gesondert betrachtet werden. Wenn wir die Geschwindigkeit des Gedächtnisses als die Geschwindigkeit eines Fußgängers betrachten, dann entspricht die Geschwindigkeit der Berechnungen ungefähr der Reisegeschwindigkeit eines Passagierflugzeugs.
Um diesen Unterschied auszugleichen, verfügt die Architektur über mehrere Caching-Ebenen. Der schnellste und kleinste ist der L1-Cache. Dann gibt es noch einen größeren und langsameren Cache, die zweite Ebene. Und es gibt einen sehr großen Cache, der mehrere zehn Megabyte groß sein kann, einen Cache der dritten Ebene, aber er ist der langsamste.
Aufgrund der unterschiedlichen Datenaustauschrate beim Rechnen wird der Rechencode in zwei Klassen unterteilt. Eine Klasse ist durch die Bandbreite, also die Geschwindigkeit des Datenaustauschs, begrenzt. Die zweite Klasse ist durch die Geschwindigkeit des Prozessors begrenzt. Die Grenze zwischen ihnen wird abhängig von der Anzahl der Operationen festgelegt, die mit einem Datenbyte ausgeführt werden. Dies ist normalerweise eine Konstante für bestimmten Code.
Der Großteil des rechenintensiven Codes wurde schon vor langer Zeit geschrieben, ist sehr gut optimiert und es gibt eine große Anzahl an Bibliotheken. Wenn Sie also in Ihrem Code umfangreiche Berechnungen sehen, ist es sinnvoll, nach einer Bibliothek zu suchen, die das kann Mach sie für dich.
Im Übrigen können Compiler nicht alles tun, da nur ein sehr begrenzter Prozentsatz der Ressourcen für ihre Entwicklung aufgewendet wird. Welche von ihnen entwickeln sich heute mehr oder weniger aktiv weiter, das heißt, sie unterstützen Standards und versuchen, diese einzuhalten? Dies ist das Frontend EDG, das in verschiedenen Derivaten verwendet wird, beispielsweise dem Intel-Compiler; LLVM; GNU und Frontend Microsoft.
Da es nur wenige davon gibt, unterstützen Compiler nur Frequenzkontrollmuster und Datenabhängigkeiten. Betrachten wir die Steuerung, dann handelt es sich um lineare Abschnitte und einfache Zyklen, also eine Abfolge von Anweisungen und Wiederholungen. Sie lernen Häufigkeitsabhängigkeiten von Daten durch Reduktion, wenn wir beispielsweise viele Elemente zu einem zusammenfassen, reduzieren und Element-für-Element-Aktionen für ein oder mehrere Arrays ausführen.
Was bleibt den Entwicklern übrig? Dies lässt sich grob in vier Teile unterteilen. Das erste ist die Architektur der Anwendung, Compiler sind einfach nicht in der Lage, sie für uns zu entwickeln.
Parallelisierung ist auch für Compiler eine knifflige Sache. Die Arbeit mit dem Speicher ist wirklich schwierig: Sie müssen die Architektur, die Parallelisierung und alles zusammen berücksichtigen. Darüber hinaus wissen Compiler nicht, wie sie die Qualität der Optimierung und die Geschwindigkeit des Codes richtig beurteilen können. Auch wir Entwickler müssen dies tun, eine Entscheidung treffen – weiter optimieren oder aufhören.
Im Architekturteil betrachten wir die Amortisation von Overheads, virtuellen Aufrufen, auf denen die Architektur in vielen Fällen basiert.
Lassen wir die Parallelisierung in den Klammern weg. Was die Nutzung des Speichers betrifft: Dies ist gewissermaßen auch die Abwertung und korrekte Arbeit mit Daten, deren korrekte Platzierung im Speicher. Im Hinblick auf die Effizienzbewertung sprechen wir über die Profilerstellung und die Suche nach Engpässen im Code.
Die Verwendung von Schnittstellen und abstrakten Datentypen ist eine der wichtigsten Entwurfsmethoden. Betrachten Sie einen ähnlichen Rechencode aus dem maschinellen Lernen. Dies ist ein bedingter Code, der die Prognose mit einer Gradientenmethode aktualisiert.
Wenn wir ein wenig hineinschauen und versuchen zu verstehen, was im Inneren passiert, dann haben wir eine IDerCalcer-Schnittstelle zur Berechnung der Ableitungen der Verlustfunktion und eine Funktion, die die Prognose (unsere Vorhersage) entsprechend dem Gradienten von verschiebt die Verlustfunktion.
Auf der rechten Seite der Folie sehen Sie, was das für den 2D-Fall bedeutet. Und beim maschinellen Lernen beträgt die Größe der Prognose nicht zwei oder drei, sondern Millionen, Dutzende Millionen Elemente. Sehen wir uns an, wie gut dieser Code für einen Vektor mit etwa 10 Millionen Elementen ist.
Nehmen wir die Standardabweichung als Zielfunktion und messen, wie sie funktioniert und um wie viel sie diese Prognose verschiebt. Die Ableitung dieser Zielfunktion ist auf der Folie dargestellt. Die Laufzeit auf der bedingten Maschine, die dann fest bleibt, beträgt 40 ms.
Versuchen wir zu verstehen, was hier falsch ist. Das erste, was Aufmerksamkeit erregt, sind virtuelle Anrufe. Schaut man sich den Profiler an, sieht man, dass es sich je nach Anzahl der Parameter um etwa fünf bis zehn Anweisungen handelt. Und wenn, wie in unserem Fall, die Berechnung der Ableitung selbst nur aus zwei arithmetischen Operationen besteht, kann dies leicht zu einem erheblichen Mehraufwand führen. Für einen großen Körper bei der Berechnung von Derivaten beträgt dieser ca. Für einen kurzen Körper, der eine Ableitung berechnet – sagen wir nicht einmal 500 Anweisungen, sondern 20, 50 oder noch weniger – wird dies bereits einen erheblichen Prozentsatz der Zeit ausmachen. Was zu tun ist? Versuchen wir, den virtuellen Funktionsaufruf zu puffern, indem wir die Schnittstelle ändern.
Zunächst haben wir die Ableitungen Punkt für Punkt berechnet, für jedes Element des Vektors separat. Gehen wir von der Element-für-Element-Verarbeitung zur Verarbeitung durch Vektoren über. Nehmen wir die Standard-C++-Vorlage, die es Ihnen ermöglicht, mit einer Ansicht auf einem Vektor zu arbeiten. Wenn Ihr Compiler den neuesten Standard nicht unterstützt, können Sie auch eine einfache hausgemachte Klasse verwenden, die einen Zeiger auf die Daten und die Größe enthält. Wie wird sich der Code ändern? Uns bleibt nur ein Aufruf, der die Ableitungen berechnet, und dann müssen wir eine Schleife hinzufügen, die die Prognose tatsächlich aktualisiert.
Zusätzlich zum Hinzufügen eines Zyklus müssen wir uns die Daten noch ein zweites Mal ansehen, dh den Vorhersagevektor selbst und den gerade berechneten Gradientenvektor ein zweites Mal lesen.
Lassen Sie uns noch einmal an derselben Maschine messen und sehen, was schlimmer geworden ist, etwas ist schief gelaufen. Lassen Sie uns verstehen, was mit dem Code passiert ist.
Es macht keinen Sinn, einen Zyklus von etwas zu vermuten, da dies genau das gleiche Frequenzmuster ist, das Compiler gut erkennen und optimieren. Die Anzahl der Operationen pro Datenelement wird dort geringer sein als die Kosten eines virtuellen Anrufs.
Aber das Erstellen eines großen Vektors und das erneute Durchlaufen dieses Vektors ist an dieser Stelle ein Problem zu vermuten. Um zu verstehen, warum dies schlecht ist und zu Verlangsamungen führt, müssen Sie sich vorstellen, was im Speicher passiert, wenn der Code, den wir auf der Folie rechts sehen, ausgeführt wird.
Bei der Berechnung des Vektors der Derivate kommt es zu einem Zyklus, der die Prognose verschiebt. Vor diesem Zyklus verbleibt nur ein sehr kleiner Teil der Daten im schnellen L1-Cache, der mit der Prozessorfrequenz läuft. Auf der Folie steht die Ampel auf Grün. Der Rest der Daten wird aus dem Cache in den Speicher verschoben, und wenn die Schleife die Vorhersagen aktualisiert, müssen die Daten ein zweites Mal aus dem Speicher gelesen werden. Und es funktioniert bei uns im Allgemeinen sehr langsam, mit der Geschwindigkeit eines Fußgängers.
Wenn wir die Prognose aktualisieren, müssen wir nicht alle Derivate auf einmal lesen. Es reicht aus, sie in großen Bündeln zu zählen, um virtuelle Anrufe aufzunehmen. Daher ist es sinnvoll, die Berechnung der Derivate und die Aktualisierung der Prognose in kleine Blöcke aufzuteilen und diese beiden Aktionen zu vermischen. Wozu führt das, wenn man sich anschaut, woher die Daten gelesen werden?
Dies führt dazu, dass wir ständig Daten aufnehmen und dass die Daten im L1-Cache verbleiben und keine Zeit haben, in den langsamen Speicher zu gelangen. Und dann müssen wir verstehen, wer uns diese Blockgröße verrät.
Es ist logisch, den Ableitungsrechner selbst anzuvertrauen, denn nur er weiß, wie viel Cache er benötigt. Als nächstes müssen Sie die Schleife neu schreiben, die wir durch das Array gesehen haben. Es muss in zwei Teile geteilt werden. Die äußere Schleife durchläuft die Blöcke und im Inneren durchlaufen wir die Elemente des Blocks zweimal.
Hier ist es, extern nach Blöcken.
Und hier ist das innere Element des Blocks.
Wir berücksichtigen, dass der letzte Block möglicherweise unvollständig ist.
Mal sehen, was passiert. Wir sehen, dass wir richtig geraten haben, richtig verstanden haben, was passierte, und auf Kosten eher kleiner Änderungen im Code die Arbeitszeit um acht Prozent reduziert haben. Aber wir können noch mehr tun. Wir müssen noch einmal kritisch hinterfragen, was wir geschrieben haben. Schauen Sie sich die Funktion an, die die Ableitungen für uns berechnet. Es gibt uns einen Vektor von Derivaten zurück, deren Elemente in ungünstigen Situationen nur langsam zugänglich sein werden.
Es gibt zwei Gründe. Zuerst wird ein Vektor auf dem Heap zugewiesen. Die Chancen stehen gut, dass dieser Vektor wiederholt erstellt und zerstört wird. Der zweite Nachteil in Bezug auf die Geschwindigkeit ist, dass wir jedes Mal Speicher erhalten, wahrscheinlich an einer neuen Adresse. Dieser Speicher ist aus Sicht des Caches „kalt“, d. h. vor dem Schreiben in ihn führt der Prozessor einen Hilfslesevorgang durch, um die Daten im Cache zu initialisieren.
Um dies zu beheben, müssen wir die Zuordnung aus der Schleife verschieben. Dazu müssen wir die Schnittstelle erneut ändern, die Rückgabe von Vektoren stoppen und damit beginnen, Ableitungen in den Speicher zu schreiben, die wir vom aufrufenden Code erhalten.
Dies ist eine Standardtechnik – die Beseitigung aller Manipulationen mit Ressourcen aus Engpässen im Rechencode. Fügen wir der CalcDer-Methode einen weiteren Parameter hinzu, nämlich den Vektor, in den die Ableitungen fallen sollen.
Der Code wird sich auch auf offensichtliche Weise ändern. Der Ableitungsvektor ist außerhalb aller Schleifen eins, und der Methode wird einfach ein neuer Parameter hinzugefügt.
Schauen Sie. Es stellt sich heraus, dass wir im Vergleich zur Vorgängerversion noch einmal rund acht Prozent und im Vergleich zur Basisversion bereits 15 % gewonnen haben.
Es ist klar, dass Optimierungen nicht auf die Amortisierung von Gemeinkosten beschränkt sind, sondern dass es auch andere Arten von Engpässen gibt.
Um zu veranschaulichen, wie man Engpässe findet, benötigen wir noch einen einfachen experimentellen Code. Ich habe zum Beispiel die Matrixtransposition genommen. Wir haben eine approx-Matrix und eine approxByCol-Matrix, in die wir die transponierten Daten einfügen müssen. Und ein einfaches Nest aus zwei Zyklen. Hier gibt es keine virtuellen Aufrufe zum Erstellen von Vektoren. Es ist nur eine Datenübertragung. Die Schleife ist für den Compiler relativ praktisch.
Lassen Sie uns messen, wie dieser Code auf einer ausreichend großen Matrix und auf einer bestimmten Maschine funktioniert.
Ich habe zum Beispiel die Anzahl der Zeilen 1000 und die Anzahl der Spalten 100.000 angenommen. Die Maschine ist ein Intel-Server, ein Kern. Der Speicher ist genau so, er ist wichtig für uns, denn alle Arbeiten mit dem Speicher und die Geschwindigkeit hängen von der Geschwindigkeit des Speichers ab. Wir haben gemessen und 1,4 s erhalten. Ist es viel oder wenig? Was können wir in dieser Zeit tun?
Wir schaffen es, 800 Megabyte zu lesen, das ist keine transponierte Matrix, sondern das Original. Und auch 1,6 GB lesen und schreiben, das ist schon eine transponierte Matrix. Der Prozessor führt vor dem Schreiben einen Hilfslesevorgang durch, um die Daten im Cache zu initialisieren.
Lassen Sie uns berechnen, wie viel Bandbreite wir mit Nutzen genutzt haben. Es stellte sich heraus, dass der Durchsatz unseres Codes 1,7 GB/s betrug.
Dies war eine theoretische Berechnung. Dann können Sie einen Profiler nehmen, der die Arbeitsgeschwindigkeit mit dem Speicher messen kann. Ich habe VTune genommen. Mal sehen, was es zeigt. Zeigt eine ähnliche Zahl — 1,8 GB. Im Prinzip stimmt es gut, denn bei unserer Berechnung haben wir nicht berücksichtigt, dass wir Zeilenadressen und Spaltenadressen lesen müssen. Außerdem registriert VTune Hintergrundaktivitäten beim Betriebssystem. Daher stimmt unser Modell mit der Realität überein.
Um zu verstehen, ob 1,7 GB viel oder wenig sind, müssen wir herausfinden, welche maximale Bandbreite uns zur Verfügung steht.
Dazu müssen Sie die Spezifikationen des Prozessors lesen. Es gibt eine spezielle Website ark.intel.com, auf der Sie alles über jeden Prozessor erfahren können. Wenn wir uns unseren Server genauer ansehen, sehen wir, dass er über acht Kerne verfügt und für den schnellsten DDR3-Speicher, den er unterstützt, eine Übertragungsgeschwindigkeit von etwa 60 GB/s in eine Richtung erreicht.
Hier müssen wir jedoch berücksichtigen, dass wir nur einen Kern verwenden und unser Speicher langsamer ist, d. h. wir müssen diese 60 GB im Verhältnis zur Anzahl der Kerne und der Speicherfrequenz an unsere Verhältnisse skalieren.
Unser Code könnte also 5,3 GB in eine Richtung verbrauchen. Und da man parallel lesen und schreiben kann, würden wir im Idealfall 10,6 erreichen, wenn wir nur Daten von Ort zu Ort kopieren würden. Da wir zwei Lesevorgänge und einen Schreibvorgang haben, sollten es etwa 8 GB/s sein. Wir erinnern uns, dass wir 1,7 erreicht haben. Das heißt, wir haben ungefähr 20 % verbraucht.
Warum ist das so? Auch hier müssen Sie sich mit der Architektur auseinandersetzen. Tatsache ist, dass die Daten zwischen Speicher und Cache nicht in beliebigen Paketen übertragen werden, sondern in genau 64 Bytes, nicht mehr und nicht weniger. Dies ist die erste Überlegung.
Die zweite Überlegung besteht darin, dass wir die transponierten Daten nicht sequentiell, sondern zufällig schreiben, da die Zeilen der Matrix auf unvorhersehbare Weise im Speicher liegen.
Es stellt sich heraus, dass wir vor dem Schreiben einer reellen Zahl 64 Datenbytes subtrahieren müssen. Wenn wir die Größe der Matrix mit N bezeichnen, erhalten wir anstelle der optimalen Betriebszeit (N / 5,3 + N / 10,6) (8 * N / 5,3 + N / 10,6). Irgendwo vier- bis fünfmal mehr, was diesen Wirkungsgrad von 20 % erklärt.
Was tun dagegen? Sie müssen aufhören, Daten in eine Spalte zu schreiben, und mit dem Schreiben so vieler Spalten beginnen, wie in eine Cache-Zeile (64 Byte) passen. Dazu teilen wir den Zyklus über Spalten in einen Zyklus über Cache-Zeilen und eine verschachtelte Schleife über Cache-Zeilenelemente auf.
Hier sind sie, Iterationen über die Cash-Linien.
Und hier sind sie, Iterationen innerhalb der Cache-Zeile. Der Einfachheit halber gehen wir hier davon aus, dass die Daten an der Grenze der Cache-Zeile ausgerichtet sind. Lassen Sie uns nun mit VTune überprüfen, was passiert.
Wir sehen, dass es nahe an den berechneten acht Gigabyte pro Sekunde lag – 7,6. Aber es ist noch keine Tatsache, dass all diese 7.6 nützliche Arbeiten sind. Vielleicht sind einige davon über Kopf.
Um zu verstehen, welchen Nutzen wir erzielt haben, messen wir die Laufzeit nach der Optimierung. Es stellt sich heraus, dass es auf derselben Maschine 0,5 s dauert. Der Durchsatz, der auf die Transposition selbst zurückzuführen ist, beträgt 4,8 GB/s. Es ist ersichtlich, dass es immer noch einen Spielraum gibt, den wir nicht gewählt haben, aber trotzdem haben wir aus 20 Prozent Effizienz 60 Prozent bekommen.
Mit dem Profiler können Sie herausfinden, warum wir nicht 80 % oder 95 % erreicht haben.
Tatsache ist, dass wir Matrizen als Vektoren von Vektoren speichern, das heißt, wir verwenden den Speicherzugriff mit doppelter Indirektion.
Mit VTune können Sie sehen, welche Anweisungen generiert werden, um auf Array-Elemente zuzugreifen. Anweisungen, die die Adressen der Spalten der transponierten Matrix subtrahieren, sind links gelb hervorgehoben. Und das sind erstens zusätzliche Anweisungen und zweitens zusätzliche Datenübertragungen. Wir werden aber nicht weiter optimieren, sondern innehalten und zusammenfassen.
Was habe ich dir heute erzählt? Ein nützlicher Tipp für die Arbeit mit Computercode ist die blockweise Verarbeitung, um die Gemeinkosten zu amortisieren, die beispielsweise mit virtuellen Anrufen verbunden sind.