Der Begriff "Web -Skala" wurde in letzter Zeit aufermaßen, und die Menschen machen ihre Systeme auch "breiter Domäne", indem sie ihre Anwendungsarchitektur erweitern. Aber was genau ist eine vollständige Domain? Oder wie können Sie die gesamte Domäne sicherstellen?
Die am meisten gehypte Skalierungslast ist die beliebteste Domäne. Beispielsweise können Systeme, die den Zugriff auf den einzelnen Benutzer unterstützen, auch 10, 100 oder sogar 1 Million Benutzerzugriff unterstützen. Im Idealfall sollte unser System so "staatslos" wie möglich bleiben. Selbst wenn ein Zustand existieren muss, kann er an verschiedenen Verarbeitungsanschlüssen des Netzwerks umgewandelt und übertragen werden. Wenn die Last zum Engpass wird, kann es keine Verzögerung geben. Für eine einzige Anfrage ist es also akzeptabel, 50 bis 100 Millisekunden zu nehmen. Dies wird als Skalierung bezeichnet.
Die Erweiterung führt bei der Optimierung der vollständigen Domänen völlig unterschiedlich. Beispielsweise kann ein Algorithmus, der sicherstellt, dass eine Daten erfolgreich verarbeitet werden, auch 10, 100 oder sogar 1 Million Daten erfolgreich verarbeiten. Die Komplexität des Ereignisses (große O -Symbole) ist die beste Beschreibung, unabhängig davon, ob dieser metrische Typ machbar ist oder nicht. Latenz ist ein Leistungsskalierungsmörder. Sie werden Ihr Bestes geben, um alle Vorgänge auf derselben Maschine zu verarbeiten. Dies wird als Skalierung bezeichnet.
Wenn Kuchen vom Himmel fallen kann (natürlich ist das unmöglich), können wir möglicherweise horizontale Expansion und vertikale Expansion kombinieren. Heute werden wir jedoch nur die folgenden einfachen Möglichkeiten einführen, um die Effizienz zu verbessern.
Sowohl Forkjoinpool in Java 7 als auch parallele Datenströme in Java 8 (Parallelstream) sind hilfreich für die parallele Verarbeitung. Es ist besonders offensichtlich, wenn Java-Programme auf Multi-Core-Prozessoren bereitgestellt werden, da alle Prozessoren auf denselben Speicher zugreifen können.
Daher besteht der grundlegende Nutzen dieser parallele Verarbeitung darin, die Latenz im Vergleich zur Skalierung verschiedener Maschinen in allen Netzwerken nahezu vollständig zu beseitigen.
Aber lassen Sie sich nicht durch die Auswirkungen der parallelen Verarbeitung verwirrt! Bitte beachten Sie die folgenden zwei Punkte:
Die Reduzierung der Algorithmuskomplexität ist zweifellos der effektivste Weg, um die Leistung zu verbessern. Beispielsweise sind für eine HashMap -Instanz -Lookup () -Methode die Ereigniskomplexität o (1) oder die Raumkomplexität o (1) am schnellsten. Aber diese Situation ist oft unmöglich, geschweige denn leicht zu erreichen.
Wenn Sie die Komplexität des Algorithmus nicht verringern können, können Sie auch die Leistung verbessern, indem Sie wichtige Punkte im Algorithmus finden und die Methoden verbessern. Angenommen, wir haben das folgende Algorithmusdiagramm:
Die Gesamtzeitkomplexität dieses Algorithmus ist O (N3), und wenn er in einer separaten Zugriffsreihenfolge berechnet wird, kann auch der Schluss gezogen werden, dass die Komplexität O (N x O x P) ist. Trotzdem werden wir einige seltsame Szenarien finden, wenn wir diesen Code analysieren:
Ohne Produktionsdatenreferenz können wir leicht Schlussfolgerungen ziehen, dass wir "High-Overhead-Operationen" optimieren sollten. Die Optimierungen, die wir vorgenommen haben, hatten jedoch keinen Einfluss auf die gelieferten Produkte.
Die optimierte goldene Regel ist nichts anderes als Folgendes:
Das ist alles für die Theorie. Unter der Annahme, dass wir festgestellt haben, dass das Problem am rechten Zweig auftritt, ist es wahrscheinlich, dass die einfache Verarbeitung im Produkt aufgrund einer großen Zeitspanne die Reaktion verliert (unter der Annahme, dass die Werte von N, O und P sehr groß sind), bitte beachten Sie, dass die Zeitkomplexität des in dem Artikel erwähnten linken Zweigs O (N3) ist. Die hier unternommenen Anstrengungen können nicht erweitert werden, können aber bis später Zeit für Benutzer sparen und schwierige Leistungsverbesserungen verzögern.
Hier sind 10 Tipps zur Verbesserung der Java -Leistung:
1. Verwenden Sie StringBuilder
Stingbuilder sollte standardmäßig in unserem Java -Code verwendet werden, und der + Operator sollte vermieden werden. Vielleicht haben Sie unterschiedliche Meinungen zum Syntaxzucker von StringBuilder, wie z. B.:
String x = "a" + args.length + "b";
Wird zusammengestellt als:
0 New Java.Lang.StringBuilder [16] 3 DUP4 LDC <String "A"> [18] 6 Invokespecial Java.Lang.StringBuilder (Java.Lang.String) [20] 9 ALOAD_0 [Args] 10 ArrayLength11 Invokevirtual Java.lang.Lang.Strang.Strang.Strang. [23] 14 LDC <String "B"> [27] 16 InvokeVirtual Java.lang.StringBuilder.Append (Java.Lang.String): Java.lang.StringBuilder [29] 19 Invokevirtual Java.Lang.StringBuilder.toString (): Java.Lang.Lang.Lang.Lang.Lang.Lang.Lang.Lang.Lang.Lang.
Aber was genau ist passiert? Müssen Sie die folgenden Abschnitte verwenden, um die Zeichenfolge als nächstes zu verbessern?
String x = "a" + args.length + "b"; if (argsgth == 1) x = x + args [0];
Jetzt haben wir den zweiten StringBuilder verwendet, der keinen zusätzlichen Speicher auf dem Haufen verbraucht, sondern Druck auf die GC ausübt.
StringBuilder x = new StringBuilder ("a"); X.Append (args.Length); X.Append ("B"); if (argsgth == 1); X.Append (args [0]);Wenn Sie sich im obigen Beispiel auf den Java -Compiler verlassen, um die Instanz implizit zu generieren, hat der kompilierte Effekt nichts damit zu tun, ob die StringBuilder -Instanz verwendet wird. Bitte denken Sie daran: Im NOPE -Zweig wird die Zeit, die für jeden CPU -Zyklus aufgewendet wird, für GC oder für den Standardraum für StringBuilder aufgewendet, und wir verschwenden n x o x p -Zeit.
Im Allgemeinen ist die Verwendung von StringBuilder besser als die Verwendung des + Operators. Wählen Sie nach Möglichkeit StringBuilder, wenn Sie Referenzen über mehrere Methoden hinweg weitergeben müssen, da die String zusätzliche Ressourcen verbraucht. JOOQ verwendet diese Methode, um komplexe SQL -Anweisungen zu generieren. Während des gesamten SQL-Pass-Through des abstrakten Syntaxbaums wird nur ein StringBuilder verwendet.
Noch tragischer ist, dass Sie StringBuilder anstelle von StringBuffer verwenden, wenn Sie noch StringBuffer verwenden. Schließlich gibt es wirklich nicht viele Fälle, in denen Saiten synchronisiert werden müssen.
Regelmäßige Ausdrücke vermitteln den Menschen den Eindruck, dass sie schnell und einfach sind. Die Verwendung regulärer Ausdrücke im NOPE -Zweig wäre jedoch die schlimmste Entscheidung. Wenn Sie regelmäßige Ausdrücke im rechenintensiven Code verwenden müssen, zwischen dem Muster zumindest zwischen dem Muster einzuspeichern, um das Muster wiederholt zu erfassen.
statisches endgültiges Muster heavy_regex = muster.comPile ("((((x)*y)*z)*");Wenn nur ein einfacher regulärer Ausdruck wie folgt verwendet wird:
String [] parts = ipaddress.split ("//.");Dies ist am besten, um ein normales Char [] -Array oder ein indexbasiertes Betrieb zu verwenden. Zum Beispiel spielt der folgende Code mit schlechter Lesbarkeit tatsächlich die gleiche Rolle.
int länge = ipaddress.length (); int offset = 0; int part = 0; für (int i = 0; i <Länge; i ++) {if (i == Länge - 1 || iPaddress.Charat (i+1) == '.') {part] = ipaddress.substring (Offset, i+1);Der obige Code zeigt auch, dass eine vorzeitige Optimierung bedeutungslos ist. Obwohl im Vergleich zur Split () -Methode, ist dieser Code weniger aufrechterhalten.
Herausforderung: Können kluge Freunde schnellere Algorithmen entwickeln?
Regelmäßige Ausdrücke sind sehr nützlich, müssen aber auch einen Preis zahlen, wenn sie verwendet werden. Vor allem, wenn Sie tief in NOPE -Zweigen sind, sollten Sie vermeiden, dass regelmäßige Ausdrücke um alle Kosten verwendet werden. Achten Sie auch auf verschiedene JDK -String -Methoden, die reguläre Ausdrücke verwenden, wie z. B. String.replaceall () oder String.split (). Sie können eine beliebtere Entwicklungsbibliothek wie Apache Commons Lang verwenden, um String -Operationen durchzuführen.
Dieser Vorschlag gilt nicht für allgemeine Anlässe, sondern nur für Szenarien, die tief im Nop -Zweig sind. Trotzdem sollten Sie etwas verstehen. Die Schreibmethode der Java 5 -Formatschleife ist so bequem, dass wir die interne Schleifenmethode vergessen können, z. B.:
für (Stringwert: Zeichenfolgen) {// Machen Sie hier etwas Nützliches}}Wenn der Code in dieser Schleife ausgeführt wird, erstellt der Code automatisch eine Instanz des Iterators. Wenn Sie ArrayList verwenden, verteilt die virtuelle Maschine dem Objekt auf dem Haufen automatisch 3 Ganzzahl -Typ.
Private Klasse ITR implementiert Iterator <E> {int Cursor; int lastret = -1; int erwartModcount = modcount; // ...Sie können auch die folgende äquivalente Schleifenmethode verwenden, um die oben genannte für die Schleife zu ersetzen und nur ein Stück plastischer Chirurgie am Stapel zu "verschwenden", was ziemlich kostengünstig ist.
int size = strings.size (); für (int i = 0; i <size; i ++) {String -Wert: Strings.get (i); // hier etwas Nützliches tun}Wenn sich der Wert der Zeichenfolge in der Schleife nicht stark ändert, können Sie auch Arrays verwenden, um die Schleife zu implementieren.
für (String Value: StringArray) {// Machen Sie hier etwas Nützliches}}Ob aus der Perspektive des einfachen Lesens und Schreibens oder aus der Sicht des API -Designs sind Iteratoren, iterbare Schnittstellen und Foreach -Schleifen sehr nützlich. Bei den Kosten wird bei der Verwendung jedoch ein zusätzliches Objekt für jedes Schleifenkind auf dem Haufen erstellt. Wenn die Schleife mehrfach ausgeführt werden soll, achten Sie bitte vorsichtig, um bedeutungslose Instanzen zu generieren. Verwenden Sie am besten die grundlegende Zeiger-Schleifenmethode, um die oben genannte Iterator, die iterbare Schnittstelle und die für die LEACH-Schleife zu ersetzen.
Einige Meinungen, die sich gegen das obige Einwände erheben (insbesondere das Ersetzen von Iteratoren durch Zeiger), werden auf Reddit diskutiert.
Einige Methoden sind teuer. Als Beispiel haben wir die relevanten Methoden der Blätter nicht erwähnt, aber diese finden Sie nicht. Nehmen wir an, unser JDBC -Treiber muss alle Schwierigkeiten überwinden, um den Rückgabewert des Ergebnissets zu berechnen. Das SQL -Framework, das wir selbst implementieren, können so aussehen:
if (type == integer.class) {result = (t) warnull (rs, Integer NULL: Wert;}In der obigen Logik wurde die Methode von Ergebnisset.null () jedes Mal aufgerufen, wenn der INT -Wert aus dem Ergebnissatz erhalten wird, die Methode von getInt () wird jedoch definiert als:
Rückgabetyp: variabler Wert; Wenn das SQL -Abfrageergebnis NULL ist, geben Sie 0 zurück.
Ein einfacher und effektiver Weg, um es zu verbessern, ist also wie folgt:
Static Final <T erweitert die Zahl> t wesnull (Ergebnisset Rs, T -Wert) löst SQLEXception {return (value == null || (value.intValue () == 0 && rs.Wasnull ()) aus? NULL: Wert;}Dies ist ein Kinderspiel.
Cache -Methodenaufrufe, um hohe Overhead -Methoden für Blattknoten zu ersetzen, oder vermeiden Sie, dass das Aufrufen von Hochschulverfahren, wenn die Methodenkonvention dies zulässt.
In der obigen Einführung wird eine große Anzahl von Generika in dem Beispiel von JOOQ verwendet, was zur Verwendung von Byte-, Short-, INT- und Long -Wrapper -Klassen führt. Zumindest Generika sollten jedoch keine Codebeschränkung sein, bis sie sich auf Java 10- oder Valhalla -Projekte spezialisiert haben. Da es durch die folgende Methode ersetzt werden kann:
// auf der Heap Ganzzahl aufbewahrt i = 817598;
... Wenn Sie so schreiben:
// Auf dem Stack int i = 817598 speichern;
Die Dinge können bei der Verwendung von Arrays schlechter werden:
// Drei Objekte wurden auf der Heap Integer [] i = {1337, 424242} erzeugt;... Wenn Sie so schreiben:
// Es wird nur ein Objekt auf dem Heap int [] i = {1337, 424242} generiert;Wenn wir tief im NOPE -Zweig sind, sollten wir unser Bestes geben, um die Verwendung von Verpackungsklassen zu vermeiden. Der Nachteil davon ist, dass GC viel Druck ausübt. GC ist sehr damit beschäftigt, Objekte zu löschen, die von der Verpackungsklasse generiert werden.
Eine effektive Optimierungsmethode besteht daher darin, grundlegende Datentypen und Arrays fester Länge zu verwenden und eine Reihe von geteilten Variablen zu verwenden, um die Position des Objekts im Array zu identifizieren.
TROVE4J, das dem LGPL -Protokoll folgt, ist eine Java -Sammlungsklassenbibliothek, die uns eine bessere Leistung implementieren als die Forming -Array int [].
Die folgende Ausnahme ist für diese Regel: Da Boolesche und Byte -Typen nicht ausreichen, um JDK eine Cache -Methode dafür bereitzustellen. Wir können auf diese Weise schreiben:
Boolean a1 = wahr; // ... Syntaxzucker für: boolean a2 = boolean.valueof (true); byte b1 = (byte) 123; // ... Syntaxzucker für: Byte b2 = byte.Valueof ((byte) 123);
Andere grundlegende Arten von Ganzzahlen haben auch ähnliche Situationen wie Char, Short, Int und Long.
Boxen Sie diese ganzzahligen primitiven Typen nicht automatisch, wenn Sie Konstruktoren aufrufen, oder rufen Sie die Methode thetype.Valueof () auf.
Rufen Sie auch Konstruktoren in Wrapper -Klassen nicht an, es sei denn, Sie möchten eine Instanz erhalten, die nicht auf dem Haufen erstellt wird. Der Vorteil, dies zu tun, besteht darin, Ihnen Ihren Kollegen einen großen Witz des Aprilscherzes zu geben.
Natürlich, wenn Sie die Off-HEAP-Funktionsbibliothek erleben möchten, obwohl dies mit vielen strategischen Entscheidungen und nicht mit der optimistischsten lokalen Lösung gemischt werden kann. Für einen interessanten Artikel über den von Peter Lawrey und Ben Cotton geschriebenen nicht heap-Speicher klicken Sie bitte: OpenJDK und HashMap-Lassen Sie die Veteranen sicher (nicht heap!) Neue Techniken meistern.
Heutzutage fördern funktionale Programmiersprachen wie Scala Rekursion. Weil Rekursion normalerweise bedeutet, dass die Schwanzreziation in individuell optimierte Personen zerlegt werden kann. Es wäre besser, wenn die von Ihnen verwendete Programmiersprache sie unterstützen kann. Achten Sie jedoch darauf, dass die leichte Einstellung des Algorithmus die Schwanzrekursion in eine gewöhnliche Rekursion verwandelt.
Hoffentlich kann der Compiler dies automatisch erkennen, sonst hätten wir viele Stapelrahmen für Dinge verschwendet, die mit nur wenigen lokalen Variablen geschehen können.
In diesem Abschnitt gibt es nichts zu sagen, außer dass das Iterieren so weit wie möglich im NOPE -Zweig statt der Rekursion iteriert wird.
Wenn wir über eine Karte in Schlüsselwertpaaren iterieren möchten, müssen wir einen guten Grund für den folgenden Code finden:
for (k key: map.keyset ()) {v value: map.get (Schlüssel);}Ganz zu schweigen von der folgenden Schreibmethode:
für (Eintrag <k, v> Eintrag: map.enterrySet ()) {k key = Eintrag.getkey (); v value = Eintrag.getValue ();}Wenn wir NOPE -Zweig verwenden, sollten wir Karte mit Vorsicht verwenden. Weil viele Zugriffsvorgänge, die eine zeitliche Komplexität von O (1) zu haben scheinen, tatsächlich aus einer Reihe von Operationen bestehen. Und der Zugang selbst ist nicht frei. Zumindest, wenn Sie MAP verwenden müssen, müssen Sie die Einstiegsmethode () verwenden, um zu iterieren! Auf diese Weise möchten wir nur auf eine Instanz von MAP.Entry zugreifen.
Wenn es notwendig ist, die Karte in Form von Schlüsselwertpaaren über die Karte zu iterieren, sollten Sie die Eintrags-Methode () verwenden.
8. Verwenden Sie Enumset oder Enummap
In einigen Fällen, beispielsweise bei Verwendung einer Konfigurationskarte, wissen wir möglicherweise im Voraus den in der Karte gespeicherten Schlüsselwert. Wenn dieser Schlüsselwert sehr gering ist, sollten wir in Betracht ziehen, Enumset oder Enummap zu verwenden, anstatt das von uns üblicherweise verwendete Hashset oder HashMap zu verwenden. Der folgende Code enthält eine klare Erklärung:
Private Transient Object [] vals; public v put (k key, v -Wert) {// ... int index = key.ordinal (); vals [index] = masknull (Wert); // ...}Die wichtigste Implementierung des vorherigen Codes besteht darin, dass wir Arrays anstelle von Hash -Tabellen verwenden. Insbesondere wenn Sie neue Werte in die Karte einfügen, müssen Sie lediglich eine vom Compiler für jeden Enum -Typ generierte konstante Sequenznummer erhalten. Wenn es eine globale Kartenkonfiguration gibt (z. B. nur eine Instanz), erzielt Enummap unter dem Druck der zunehmenden Zugangsgeschwindigkeit eine herausragende Leistung als HashMap. Der Grund dafür ist, dass ENUMMAP einen Bit weniger Heap -Speicher als HashMap verwendet, und HashMap ruft die HashCode () -Methode und die Equals () -Methode für jeden Schlüsselwert auf.
Enum und Enumap sind enge Freunde. Wenn wir Schlüsselwerte verwenden, die ähnlich wie enumähnliche Strukturen ähnlich sind, sollten wir in Betracht ziehen, diese Schlüsselwerte als Enum-Typen zu deklarieren und sie als Enummap-Tasten zu verwenden.
Für den Fall, dass ENUMMAP nicht verwendet werden kann, müssen zumindest die Methoden von HashCode () und Equals () optimiert werden. Eine gute HashCode () -Methode ist erforderlich, da sie unnötige Aufrufe der High-Overhead Equals () -Methode verhindert.
In der Vererbungsstruktur jeder Klasse sind einfache Objekte erforderlich, die leicht akzeptabel sind. Schauen wir uns an, wie JOOQs org.jooq.table implementiert wird?
Die einfachste und schnellste HashCode () Implementierungsmethode ist wie folgt:
// AbstractTable Eine grundlegende Implementierung einer allgemeinen Tabelle: @Overridepublic int HashCode () {// [#1938] Im Vergleich zu den Standard -Queryparts ist dies eine effizientere HashCode () -implementierung. Return name.hashcode ();}Name ist der Tabellenname. Wir müssen nicht einmal Schema oder andere Tabellenattribute berücksichtigen, da die Tabellennamen in der Datenbank normalerweise eindeutig sind. Und der variable Name ist eine Zeichenfolge, die selbst einen HashCode () -Wert bereits zwischengespeichert hat.
Kommentare in diesem Code sind sehr wichtig, da AbstractTable von AbstractQuerypart die grundlegende Implementierung eines abstrakten Syntaxbaumelements ist. Gewöhnliche abstrakte Syntaxbaumelemente haben keine Attribute, daher können sie keine Fantasie zur Optimierung der HashCode () -Methode -Implementierung haben. Die überschriebene HashCode () -Methode lautet wie folgt:
// AbstractQueryPart Eine allgemeine abstrakte Syntaxbaum grundlegende Implementierung: @Overridepublic int HashCode () {// Dies ist eine funktionierende Standardimplementierung. // Die spezifische Implementierungsunterklasse sollte diese Methode überschreiben, um die Leistung zu verbessern. return create (). renderinline (this) .hashCode ();}Mit anderen Worten, der gesamte SQL -Rendering -Workflow wird ausgelöst, um den Hash -Code für ein normales abstraktes Syntaxbaumelement zu berechnen.
Die Equals () -Methode ist noch interessanter:
// grundlegende Implementierung der abstrakten allgemeinen Tabelle: @Overridepublic boolean gleich (Objekt, dass) {if (this == that th) {return true;} // [#2144] Bevor Sie die High-Overhead-AbstractQuerypart.equals () -Methode aufrufen, können Sie früh wissen, ob die Objekte nicht gleich sind. if (diese Instanz von AbstractTable) {if (Stringutils.equals (Name, (AbstractTable <?>) Das) .Name)))) {return Super.equals (das);} return false;} return false;}Verwenden Sie zunächst nicht die Equals () -Methode zu früh (nicht nur in NOPE), wenn:
Hinweis: Wenn wir Instanzen von zu früh verwenden, um kompatible Typen zu überprüfen, enthalten die folgenden Bedingungen tatsächlich Argument == NULL. Ich habe dies bereits in meinem vorherigen Blog erklärt. Siehe 10 exquisite Java -Coding -Best Practices.
Nach unserem Vergleich der oben genannten Situationen sollten wir in der Lage sein, einige Schlussfolgerungen zu ziehen. Beispielsweise wird die Methode von table.equals () von JOOQ verwendet, um zu vergleichen, ob die beiden Tabellen gleich sind. Unabhängig vom spezifischen Implementierungstyp müssen sie denselben Feldnamen haben. Beispielsweise können die folgenden zwei Elemente nicht gleich sein:
Wenn wir leicht feststellen können, ob der eingehende Parameter gleich der Instanz selbst ist (dies), können wir die Operation aufgeben, wenn das Ergebnis falsch ist. Wenn das Rückgabeergebnis zutrifft, können wir die Superumsetzung der übergeordneten Klasse weiter beurteilen. In dem Fall, in dem die meisten verglichenen Objekte nicht gleich sind, können wir die Methode so früh wie möglich beenden, um die CPU -Ausführungszeit zu speichern.
Einige Objekte haben eine höhere Ähnlichkeit als andere.
In JOOQ werden die meisten Tabelleninstanzen von JOOQs Codegenerator generiert, und die Equals () -Methode dieser Instanzen wurde zutiefst optimiert. Dutzende anderer Tabellentypen (abgeleitete Tabellen), Tabellen-Wert-Funktionen, Array-Tabellen, Verbindungstabellen, Pivot-Tabellen, gemeinsame Tabellenausdrücke usw. behalten die grundlegende Implementierung der Equals () -Methode auf.
Schließlich gibt es eine andere Situation, die auf alle Sprachen und nicht nur auf Java angewendet werden kann. Darüber hinaus ist der von uns zuvor untersuchte NOPE -Zweig auch hilfreich, um von O (N3) bis O (N log N) zu verstehen.
Leider verwenden viele Programmierer einfache lokale Algorithmen, um das Problem zu berücksichtigen. Sie sind es gewohnt, Probleme Schritt für Schritt zu lösen. Dies ist die "Ja/oder" Form des Imperatives. Dieser Programmierstil ist leicht zu modellieren "größeres Bild" bei der Konvertierung von reinen imperativen Programmierungen in die objektorientierte Programmierung in funktionale Programmierung, aber diesen Stilen fehlt das, was nur in SQL und R vorhanden ist:
Deklarative Programmierung.
In SQL können wir den Effekt an die Datenbank deklarieren, ohne den Einfluss des Algorithmus zu berücksichtigen. Die Datenbank kann den besten Algorithmus entsprechend dem Datentyp wie Einschränkungen, Schlüssel, Indizes usw. anwenden.
Theoretisch hatten wir zuerst grundlegende Ideen nach SQL und relationalen Berechnungen. In der Praxis haben die SQL-Anbieter in den letzten Jahrzehnten in den letzten Jahrzehnten überkosten effiziente Optimierer (cost-basierte Optimierer) implementiert. Dann haben wir in der Version 2010 endlich das gesamte Potenzial von SQL entdeckt.
Wir müssen SQL jedoch nicht mit der SET -Methode implementieren. Alle Sprachen und Bibliotheken unterstützen Sätze, Sammlungen, Taschen und Listen. Der Hauptvorteil der Verwendung von SET ist, dass es unseren Code präzise und klar machen kann. Zum Beispiel die folgende Schreibmethode:
Someset schneidet etwas anderen Set
Stattdessen
// Die vorherige Schreibmethode von Java 8 set Ergebnis = new Hashset (); Für (Objektkandidat: Someset), wenn (einhöhe (einige) resultiert (Kandidat).
Einige Menschen haben möglicherweise unterschiedliche Meinungen zu funktionaler Programmierung und Java 8, die uns helfen können, einfachere und einfachere Algorithmen zu schreiben. Diese Ansicht ist jedoch nicht unbedingt wahr. Wir können die imperative Java 7 -Schleife in die Stream -Sammlung von Java 8 umwandeln, aber wir verwenden immer noch denselben Algorithmus. Aber Ausdrücke im SQL-Stil sind unterschiedlich:
Someset schneidet etwas anderen SetDer obige Code kann 1000 verschiedene Implementierungen in verschiedenen Motoren haben. Wir studieren heute heute automatisch zwei Sätze in Enumset, bevor wir den Schnittbetrieb aufrufen. Auch wir können parallele Schnittvorgänge ausführen, ohne die zugrunde liegende Stream.Parallel () -Methode aufzurufen.
In diesem Artikel diskutieren wir Optimierungen über die NOPE -Verzweigung. Zum Beispiel tief in Algorithmen mit hoher Komplexität. Als Entwickler von JOOQ optimieren wir die SQL -Generation.
JOOQ ist am "Boden der Nahrungskette", da es die letzte API ist, die unser Computerprogramm beim Verlassen des JVM und der Eingabe von DBMs bezeichnet hat. Das Hotel am unteren Rand der Nahrungskette bedeutet, dass jede Linie eine Zeit nimmt, wenn sie in JOOQ ausgeführt wird. Daher möchte ich sie so schnell wie möglich optimieren.
Unsere Geschäftslogik ist möglicherweise nicht so kompliziert wie die NOPE -Filiale. Das grundlegende Framework kann jedoch sehr komplex sein (lokales SQL -Framework, lokale Bibliotheken usw.). Daher müssen wir die heute genannten Prinzipien befolgen, verwenden Sie Java Mission Control oder andere Tools, um zu überprüfen, ob es einen Bereich gibt, der optimiert werden muss.
Original -Link: Jaxenter Übersetzung: ImportNew.com - Immer auf dem Straßenübersetzung Link: http://www.importnew.com/16181.html