Warum ist es schwierig, bei der Verwendung von Bibliotheken Effizienz zu gewährleisten?


10

Jede kleine Datenbankverarbeitung kann leicht mit Python / Perl / ... -Skripten erledigt werden, die Bibliotheken und / oder sogar Dienstprogramme aus der Sprache selbst verwenden. Wenn es jedoch um Leistung geht, tendieren die Leute dazu, nach C / C ++ / Low-Level-Sprachen zu greifen. Die Möglichkeit, den Code an die Anforderungen anzupassen, scheint diese Sprachen für BigData so attraktiv zu machen - sei es in Bezug auf Speicherverwaltung, Parallelität, Festplattenzugriff oder sogar Optimierungen auf niedriger Ebene (über Assemblykonstrukte auf C / C ++ - Ebene).

Natürlich wären solche Vorteile nicht ohne Kosten: Das Schreiben des Codes und manchmal sogar die Neuerfindung des Rads kann ziemlich teuer / lästig sein. Obwohl viele Bibliotheken verfügbar sind, neigen die Benutzer dazu, den Code selbst zu schreiben, wenn sie Leistung gewähren müssen . Was deaktiviert Leistung Behauptungen von Bibliotheken während große Datenbanken verarbeiten?

Stellen Sie sich beispielsweise ein Unternehmen vor, das Webseiten kontinuierlich crawlt und die gesammelten Daten analysiert. Für jedes Schiebefenster werden unterschiedliche Data Mining-Algorithmen für die extrahierten Daten ausgeführt. Warum sollten die Entwickler auf die Verwendung verfügbarer Bibliotheken / Frameworks verzichten (sei es für das Crawlen, die Textverarbeitung und das Data Mining)? Die Verwendung bereits implementierter Inhalte würde nicht nur die Codierung des gesamten Prozesses erleichtern, sondern auch viel Zeit sparen.

In einem einzigen Schuss :

  • was macht man sich ein Schreiben des Codes Garantie der Leistung?
  • Warum ist es riskant , sich auf Frameworks / Bibliotheken zu verlassen, wenn Sie eine hohe Leistung sicherstellen müssen ?

1
Können Sie die genaue Frage klären? Vielleicht können auch einige mögliche Antworten helfen, die Sie im Sinn haben.
Amir Ali Akbari

@AmirAliAkbari SeanOwen hat eine Antwort gepostet und ich habe den Mangel an Spezifität in meiner Frage bemerkt. Ich habe seinem Beitrag einen Kommentar hinzugefügt. Bitte zögern Sie nicht, Verbesserungen am Beitrag vorzuschlagen. Andernfalls plane ich, ihn zu löschen.
Rubens

Antworten:


4

Nachdem ich das Spiel immer wieder neu geschrieben hatte (und es immer noch mache), war meine unmittelbare Reaktion Anpassungsfähigkeit .

Während Frameworks und Bibliotheken ein riesiges Arsenal an (möglicherweise miteinander verflochtenen) Routinen für Standardaufgaben haben, lassen ihre Framework-Eigenschaften häufig (immer?) Verknüpfungen zu. Tatsächlich verfügen die meisten Frameworks über eine Art Kerninfrastruktur, um die herum eine Kernschicht grundlegender Funktionen implementiert wird. Spezifischere Funktionen nutzen die Basisebene und werden in einer zweiten Ebene um den Kern platziert.

Mit Verknüpfungen meine ich nun, direkt von einer Routine der zweiten Ebene zu einer anderen Routine der zweiten Ebene zu wechseln, ohne den Kern zu verwenden. Ein typisches Beispiel (aus meiner Domain) sind Zeitstempel: Sie haben eine Datenquelle mit Zeitstempel. Bisher besteht die Aufgabe einfach darin, die Daten von der Leitung zu lesen und an den Kern weiterzuleiten, damit Ihr anderer Code sich daran erfreuen kann.

Jetzt ändert Ihre Branche das Standard-Zeitstempelformat aus einem sehr guten Grund (in meinem Fall haben sie von der Unix-Zeit zur GPS-Zeit gewechselt). Wenn Ihr Framework nicht branchenspezifisch ist, ist es sehr unwahrscheinlich, dass sie bereit sind, die Kerndarstellung der Zeit zu ändern. Daher verwenden Sie am Ende ein Framework, das fast das tut, was Sie wollen. Jedes Mal, wenn Sie auf Ihre Daten zugreifen, müssen Sie sie zuerst in das Branchenzeitformat konvertieren, und jedes Mal, wenn Sie möchten, dass sie geändert werden, müssen Sie sie wieder in das konvertieren, was der Kern für angemessen hält. Es gibt keine Möglichkeit, Daten ohne doppelte Konvertierung direkt von der Quelle an eine Senke zu übergeben.

Hier werden Ihre handgefertigten Frameworks glänzen, es ist nur eine geringfügige Änderung, und Sie modellieren wieder die reale Welt, während alle anderen (nicht branchenspezifischen) Frameworks jetzt einen Leistungsnachteil haben.

Mit der Zeit wird sich die Diskrepanz zwischen der realen Welt und dem Modell summieren. Mit einem Off-the-shelf Rahmen würden Sie bald mit Blick auf Fragen wie: Wie kann ich vertrete thisin thatoder wie mache Routine Xakzeptieren / produzieren Y.

Bisher ging es nicht um C / C ++. Wenn Sie jedoch aus irgendeinem Grund das Framework nicht ändern können, dh eine doppelte Konvertierung von Daten in Kauf nehmen müssen, um von einem Ende zum anderen zu gelangen, verwenden Sie normalerweise etwas, das den zusätzlichen Overhead minimiert. In meinem Fall wird ein TAI-> UTC- oder UTC-> TAI-Konverter am besten Raw C (oder einem FPGA) überlassen. Es ist keine Eleganz möglich, keine tiefgreifende intelligente Datenstruktur, die das Problem trivial macht. Es ist nur eine langweilige switch-Anweisung, und warum nicht eine Sprache verwenden, deren Compiler genau das gut optimieren können?


1
+1 Das mag meine Schuld sein, dass ich in meinem Beitrag nicht sehr klar bin, also hatten andere es vorher nicht bekommen. Dies ist sicherlich die Art von Antwort, nach der ich gesucht habe. Vielen Dank.
Rubens

7

Ich glaube nicht, dass jeder nach C / C ++ greift, wenn es um Leistung geht.

Der Vorteil beim Schreiben von Code auf niedriger Ebene besteht darin, dass weniger CPU-Zyklen oder manchmal weniger Speicher benötigt werden. Ich möchte jedoch darauf hinweisen, dass übergeordnete Sprachen auf untergeordnete Sprachen zurückgreifen können, um einen Teil dieses Werts zu erhalten. Python- und JVM-Sprachen können dies tun.

Die Datenwissenschaftlerin, die beispielsweise scikit-learn auf ihrem Desktop verwendet, ruft bereits stark optimierte native Routinen auf, um die Zahlenkalkulation durchzuführen. Es macht keinen Sinn, neuen Code für die Geschwindigkeit zu schreiben.

Im verteilten "Big Data" -Kontext sind Sie eher ein Engpass bei der Datenübertragung: Netzwerkübertragung und E / A. Native Code hilft nicht. Was hilft, ist nicht den gleichen Code zu schreiben, um schneller zu laufen, sondern intelligenteren Code zu schreiben.

Mit höheren Sprachen können Sie in einer bestimmten Entwicklerzeit komplexere verteilte Algorithmen implementieren als mit C / C ++. Im Maßstab schlägt der intelligentere Algorithmus mit besserer Datenbewegung dummen nativen Code.

Es ist normalerweise auch wahr, dass Entwicklerzeit und Fehler viel mehr kosten als neue Hardware. Ein Jahr der Zeit eines leitenden Entwicklers kann 200.000 US-Dollar kosten. über ein Jahr, das auch Hunderte von Servern im Wert von Rechenzeit vermietet. In den meisten Fällen ist es möglicherweise nicht sinnvoll, sich die Mühe zu machen, mehr Hardware darauf zu werfen.

Ich verstehe das Follow-up zu "Grant" und "Disable" und "Assert" nicht?


Entschuldigung für das Missverständnis. Meine Absicht war es, Antworten zu geben, wie wichtig es ist, die Kontrolle über eine Anwendung zu haben und wie diese Kontrolle durch Bibliotheken gelockert wird. Natürlich können Sie Dinge über sie annehmen (Leute schreiben Pthreads normalerweise nicht neu), aber wenn sich die Daten ändern (Laden, Durchsatz, ...), müssen Sie möglicherweise auf die lib-Quelle zugreifen, um Leistung zu gewähren. Und ja, es ist nicht unbedingt C / C ++ - obwohl dies normalerweise die für HPC ausgewählten Sprachen sind. Darf ich meine Frage löschen oder möchten Sie sie in etwas Spezifischeres ändern? Ich akzeptiere alle Vorschläge, um es zu verbessern.
Rubens

1
Nein, es ist eine gute Frage. Sie können Ihre Kommentare hier in Änderungen an der Frage wiedergeben, wenn Sie möchten.
Sean Owen

Bitte überprüfen Sie, ob die Frage jetzt Sinn macht. Ich habe einen kleinen Fall hinzugefügt, um es einfacher zu machen. Wenn Sie der Frage eine Überlegung hinzufügen möchten, können Sie sie gerne bearbeiten.
Rubens

4

Wie wir alle wissen, gibt es in der digitalen Welt viele Möglichkeiten, die gleiche Arbeit zu erledigen / erwartete Ergebnisse zu erzielen.

Und Verantwortlichkeiten / Risiken, die sich aus dem Code ergeben, liegen auf den Schultern der Entwickler.

Dies ist klein, aber ich denke, ein sehr nützliches Beispiel aus der .NET-Welt.

So viele .NET-Entwickler verwenden den integrierten BinaryReader - BinaryWriter für ihre Datenserialisierung, um Leistung zu erzielen und die Kontrolle über den Prozess zu erlangen.

Dies ist der CSharp-Quellcode der in der BinaryWriter-Klasse integrierten FrameWork-Klasse 'eine der überladenen Schreibmethoden:

// Writes a boolean to this stream. A single byte is written to the stream
// with the value 0 representing false or the value 1 representing true.
// 
public virtual void Write(bool value) 
{
     //_buffer is a byte array which declared in ctor / init codes of the class
    _buffer = ((byte) (value? 1:0));

    //OutStream is the stream instance which BinaryWriter Writes the value(s) into it.
    OutStream.WriteByte(_buffer[0]);
}

Wie Sie sehen, könnte diese Methode ohne die zusätzliche Zuweisung zur _buffer-Variablen geschrieben werden:

public virtual void Write(bool value) 
{
    OutStream.WriteByte((byte) (value ? 1 : 0));
}

Ohne Zuweisung könnten wir einige Millisekunden gewinnen. Diese wenigen Millisekunden können als "fast nichts" akzeptiert werden, aber was ist, wenn mehrere Tausend geschrieben werden (dh in einem Serverprozess)?

Nehmen wir an, dass "wenige" 2 (Millisekunden) und mehrere Tausend Instanzen nur 2.000 sind. Dies bedeutet 4 Sekunden mehr Prozesszeit. 4 Sekunden später Rückkehr.

Wenn wir weiterhin von .NET abhängig sind und Sie die Quellcodes von BCL - .NET Base Class Library - von MSDN überprüfen können, können Sie feststellen, dass der Entwickler viele Leistungsverluste feststellt.

Jeder Punkt aus der BCL-Quelle Es ist normal, dass Entwickler sich für die Verwendung von while () - oder foreach () -Schleifen entschieden haben, die eine schnellere for () -Schleife in ihrem Code implementieren könnten.

Diese kleinen Gewinne geben uns die Gesamtleistung.

Und wenn wir zur BinaryWriter.Write () -Methode zurückkehren.

Tatsächlich ist das zusätzliche Zuweisen zu einer _buffer-Implementierung kein Entwicklerfehler. Dies ist genau die Entscheidung, "in Sicherheit zu bleiben"!

Angenommen, wir entscheiden uns, _buffer nicht zu verwenden und die zweite Methode zu implementieren. Wenn wir versuchen, mit der zweiten Methode mehrere Tausend Bytes über eine Leitung zu senden (dh BLOB- oder CLOB-Daten hoch- / herunterladen), kann dies häufig fehlschlagen, da Der Verbindungsverlust. Weil wir versuchen, alle Daten ohne Überprüfungs- und Kontrollmechanismus zu senden. Wenn die Verbindung unterbrochen wird, wissen sowohl der Server als auch der Client nie, ob die gesendeten Daten abgeschlossen sind oder nicht.

Wenn der Entwickler "in Sicherheit bleiben" entscheidet, bedeutet dies normalerweise, dass die Leistungskosten von den implementierten "in Sicherheit bleiben" -Mechanismen abhängen.

Aber wenn der Entwickler entscheidet, "riskant zu werden, Leistung zu gewinnen", ist dies auch kein Fehler. Bis es einige Diskussionen über "riskante" Codierung gibt.

Und als kleine Anmerkung: Entwickler kommerzieller Bibliotheken versuchen immer, in Sicherheit zu bleiben, weil sie nicht wissen, wo ihr Code verwendet wird.


4

Aus Sicht der Programmierer zielen Frameworks selten auf die Leistung als höchste Priorität ab. Wenn Ihre Bibliothek in großem Umfang genutzt werden soll, werden die Menschen wahrscheinlich am meisten Wert auf Benutzerfreundlichkeit, Flexibilität und Zuverlässigkeit legen.

Die Leistung wird im Allgemeinen in sekundären Wettbewerbsbibliotheken geschätzt. "X-Bibliothek ist besser, weil sie schneller ist." Selbst dann tauschen diese Bibliotheken sehr häufig die optimalste Lösung gegen eine aus, die in großem Umfang genutzt werden kann.

Durch die Verwendung eines beliebigen Frameworks gehen Sie von Natur aus das Risiko ein, dass eine schnellere Lösung vorhanden ist. Ich könnte sogar sagen, dass es fast immer eine schnellere Lösung gibt.

Etwas selbst zu schreiben ist keine Garantie für die Leistung, aber wenn Sie wissen, was Sie tun und nur relativ wenige Anforderungen haben, kann dies hilfreich sein.

Ein Beispiel könnte das JSON-Parsing sein. Es gibt hundert Bibliotheken für eine Vielzahl von Sprachen, die JSON in ein referierbares Objekt verwandeln und umgekehrt. Ich kenne eine Implementierung, die alles in CPU-Registern erledigt. Es ist messbar schneller als alle anderen Parser, aber es ist auch sehr begrenzt und diese Einschränkung hängt davon ab, mit welcher CPU Sie arbeiten.

Ist die Aufgabe, einen leistungsfähigen umgebungsspezifischen JSON-Parser zu erstellen, eine gute Idee? Ich würde eine angesehene Bibliothek 99-mal von 100 nutzen. In dieser separaten Instanz würden sich einige zusätzliche CPU-Zyklen multipliziert mit einer Million Iterationen die Entwicklungszeit lohnen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.