Frage

Ich weiß, diese Frage hat gewesen getan aber ich habe eine etwas andere Wendung zu. Einige haben darauf hingewiesen, dass diese vorzeitige Optimierung ist, die ganz richtig ist, wenn ich nur für Sachlichkeit willen und Zweckmäßigkeit willen gefragt wurde. Mein Problem in einem praktischen Problem wurzelt, aber ich bin trotzdem immer noch neugierig.


Ich bin eine Reihe von SQL-Anweisungen, die Erstellung eines Skripts zu erstellen (wie in es auf der Festplatte gespeichert werden) ein Datenbank-Schema (leicht viele viele Hunderte von Tabellen, Ansichten, etc.) zu erstellen. Das bedeutet, meine String-Verkettung ist append-only. String nach MSDN, funktioniert durch einen internen Puffer hält (sicherlich ein char []) und Kopieren Zeichenfolge Zeichen hinein und Neuzuweisung das Array wie nötig.

Allerdings hat mein Code viele Wiederholungsfolgen ( "TABLE [CREATE", "GO \ n", etc.), was bedeutet, ich Vorteil von ihnen internieren werden, aber nicht, wenn ich String verwenden, da sie jedes Mal kopiert würde. Die einzigen Variablen sind im Wesentlichen Tabellennamen und so, dass bereits als Zeichenfolge in anderen Objekten bestehen, die bereits im Speicher befinden.

So so weit wie kann ich sagen, nach meiner Daten in und meine selbst erstellte Objekte gelesen wird, die das Schema Informationen dann alle meine String-Informationen halten kann durch Internierung wieder verwendet werden, nicht wahr?

Unter der Annahme, dass, dann wäre keine Liste oder LinkedList von Strings schneller sein, weil sie Hinweise auf internierten Strings behalten? Dann ist es nur ein Anruf zu String.Concat () für eine einzelne Speicherzuweisung der gesamten Zeichenfolge, die genau die richtige Länge.

würde eine Liste haben Zeichenfolge neu zuzuteilen [] von internierten Zeigern und einer verknüpften Liste haben würde Knoten erstellen und Zeiger ändern, so dass sie nicht „frei“ zu tun, aber wenn ich verketten viele tausend internierten Strings , dann würden sie scheinen, wie sie effizienter wäre.

Nehmen wir nun an ich, dass ich mit einigen heuristischen auf Zeichenanzahl für jede SQL-Anweisung kommen konnte und jede Art zählen und eine grobe Vorstellung bekommen und voreingestellter meine String Fähigkeit, seine Zeichen zu vermeiden Neuzuweisung [], aber ich würde durch überschreiten haben eine faire Marge die Wahrscheinlichkeit, eine Umschichtung zu reduzieren.

So für diesen Fall, die schnellst wäre, eine einzige verkettete Zeichenfolge zu erhalten:

     
  • String
  •  
  • Liste von internierten Strings
  •  
  • LinkedList von internierten Strings
  •  
  • String mit einer Kapazität Heuristik
  •  
  • Etwas anderes?

Als andere Frage (ich nicht immer auf die Festplatte gehen kann) zum oben Erwähnten: würde ein einzelner Stream in eine Ausgabedatei noch schneller sein? Alternativ können Sie auch eine Liste oder LinkedList dann schreibt sie in eine Datei aus der Liste anstelle von ersten Verkettungs im Speicher.

EDIT: Wie gewünscht, die Referenz (.NET 3.5) auf MSDN. Dort heißt es: "Neue Daten an das Ende des Puffers angehängt wird, wenn Raum verfügbar ist, andernfalls ein neuer, größerer Puffer zugeordnet ist, Daten aus dem ursprünglichen Puffer auf den neuen Puffer kopiert werden, dann wird die neuen Daten angehängt werden zu den neuen Puffer. " zu mir das bedeutet ein char [], dass realloced wird es größer zu machen (die alten Daten auf die Größe geänderte Array Kopieren erfordert) dann angehängt wird.

War es hilfreich?

Lösung

Für Ihre gesonderte Frage , Win32 hat ein WriteFileGather Funktion, die effizient eine Liste (Internierung) Strings auf der Festplatte schreiben könnte - aber es wäre einen bemerkenswerten Unterschied nur, wenn asynchron aufgerufen werden, da die Plattenschreib alle aber extrem große Verkettungen beschatten.

Für Ihre Hauptfrage : es sei denn, Sie Megabyte Skript erreichen, oder Zehntausende von Skripten, keine Sorge.

Sie können String erwarten, dass die Zuordnungsgröße auf jeder Neuverteilung verdoppeln. Das wäre ein Puffer von 256 Byte bis 1MB bedeuten wächst nur 12 Umverteilungen -. Recht gut, da Ihre erste Schätzung war 3 Größenordnung vom Ziel

Rein als eine Übung, einige Schätzungen: einen Puffer von 1MB Gebäude wird rund 3 MB Speicher (1MB Quelle, 1MB Ziel, 1MB kehren aufgrund Kopieren während realloation).

Eine verkettete Liste Implementierung wird über 2MB fegen, (und das wird ignoriert die 8 Byte / Objekt-Overhead pro String-Referenz). So können Sie sparen 1 MB Speicher liest / schreibt, im Vergleich zu einer typischen Speicherbandbreite von 10 Gbit / s und 1 MB L2-Cache.)

Ja, eine Liste Implementierung ist möglicherweise schneller, und der Unterschied wäre egal, ob Ihre Puffer eine Größenordnung größer sind.

Für die viel häufiger bei kleinen Strings ist die algorithmische Verstärkung vernachlässigbar und leicht durch andere Faktoren ausgeglichen: der String Code im Code-Cache bereits wahrscheinlich ist, und ein brauchbares Ziel für microoptimizations. Auch eine Zeichenfolge intern verwendet bedeutet, dass keine Kopie überhaupt, wenn die endgültige Zeichenfolge, die den anfänglichen Puffer paßt.

eine verkettete Liste verwendet auch die Neuzuteilung Problem von O bringen (Anzahl der Zeichen) bis O (Anzahl der Segmente) - die Liste der Zeichenfolge Referenzen steht das gleiche Problem wie eine Zeichenfolge


So IMO die Umsetzung der String ist die richtige Wahl, für den gemeinsamen Fall optimiert, und verschlechtert meist für unerwartet große Zielpuffer. Ich würde eine Liste Implementierung erwarten zunächst für sehr viele kleine Segmente verschlechtern, was eigentlich die extreme Art von Szenario ist Stringbuilder zu optimieren versucht.

Dennoch wäre es interessant, einen Vergleich der beiden Ideen zu sehen, und wenn die Liste schneller zu sein beginnt.

Andere Tipps

Wenn ich so etwas wie diese Umsetzung, ich würde nie einen String (oder irgendeine andere in Speicherpuffer des Skriptes) bauen. Ich würde es nur statt zu Ihrer Datei ausströmen, und alle Strings inline machen.

Hier ist ein Beispiel Pseudo-Code (nicht syntaktisch korrekt oder überhaupt):

FileStream f = new FileStream("yourscript.sql");
foreach (Table t in myTables)
{
    f.write("CREATE TABLE [");
    f.write(t.ToString());
    f.write("]");
    ....
}

Dann werden Sie nie ein in Erinnerung Darstellung des Skripts benötigen, mit all dem Kopieren von Strings.

Die Meinung?

Nach meiner Erfahrung, ich richtig zugeordnet Stringübertrifft die meisten alles andere für große Mengen von String-Daten. Es lohnt sich etwas Speicher verschwenden, auch, indem Sie Ihre Schätzung um 20% oder 30%, um Umverteilung zu verhindern überschieß. Ich habe keine Zeit harte Zahlen, um es zurück, um meine eigenen Daten auf verwenden, aber einen Blick auf diese Seite für mehr .

Doch wie Jeff darauf hinzuweisen, fond ist, nicht vorzeitig optimieren!

EDIT: Wie @Colin Burnett wies darauf hin, die Tests, die Jeff mit Brians Tests nicht durchgeführt stimmen, aber der Punkt der Verknüpfung von Jeffs Beitrag wurde über vorzeitige Optimierung im Allgemeinen. Mehrere commen Seite auf Jeffs bemerkt Probleme mit seinen Tests.

Eigentlich StringBuilder verwendet eine Instanz von String intern. String ist in der Tat wandelbar in der System Montage, weshalb StringBuilder oben drauf bauen kann. Sie können StringBuilder ein bisschen effektiver machen, indem eine angemessene Länge zuweisen, wenn Sie die Instanz erstellen. Auf diese Weise werden Sie beseitigen / die Anzahl der Resize-Operationen reduzieren.

String interning Werke für Zeichenfolgen, die zum Zeitpunkt der Kompilierung identifiziert werden kann. Wenn Sie also eine Menge von Strings während der Ausführung erzeugen werden sie nicht interniert werden, wenn Sie durch den Aufruf der Internierung Methode auf Zeichenfolge so selbst zu tun.

interning profitieren Sie nur, wenn Sie die Saiten identisch sind. Fast identische Strings nicht in den Genuss von Internierung, so "SOMESTRINGA" und "SOMESTRINGB" zwei verschiedene Strings sein, auch wenn sie interniert werden.

Wenn alle (oder die meisten) der Saiten werden interniert verkettet werden, dann wird Ihr Programm haben Sie vielleicht eine Leistungssteigerung geben, da es potentally weniger Speicher verwenden könnte, und könnte ein paar große Zeichenfolge Kopien speichern.

jedoch, ob es tatsächlich verbessert, hängt perf auf das Volumen der Daten, die Sie Verarbeitung sind, weil die Verbesserung der konstanten Faktoren ist, nicht in der Größenordnung des Algorithmus.

Der einzige Weg, um wirklich zu sagen, ist Ihre App in beiden Richtungen auszuführen und die Ergebnisse zu messen. Allerdings, wenn Sie unter erheblichen Speicherdruck sind, und müssen einen Weg Bytes zu speichern, würde ich nicht stören und würde nur String-Builder verwenden.

Ein StringBuilder verwendet keine char[] die Daten zu speichern, verwendet es ein internes wandelbar String. Das bedeutet, dass es kein zusätzlicher Schritt ist die endgültige Zeichenfolge zu erstellen, wie es ist, wenn Sie eine Liste von Strings verketten, die StringBuilder nur liefert den internen String-Puffer als regulären String.

Die Umschichtungen, die die StringBuilder die Kapazität zu erhöhen tun bedeuten, dass die Daten, die durch Durchschnitt einer zusätzlichen 1,33-mal kopiert sind. Wenn Sie eine gute Schätzung der Größe zur Verfügung stellen können, wenn Sie die StringBuilder erstellen, können Sie, dass reduzieren sogar furter.

Allerdings bekommt ein bisschen Perspektive, sollten Sie sehen, was es ist, dass Sie zu optimieren versuchen. Was wird die meiste Zeit in Ihrem Programm zu nehmen ist, um tatsächlich die Daten auf die Festplatte zu schreiben, so dass selbst wenn Sie Ihre String Handling doppelt so schnell sein wie mit einem StringBuilder optimieren können (was sehr unwahrscheinlich ist), die Gesamtdifferenz wird nur noch sein ein paar Prozent.

Haben Sie darüber nachgedacht C ++ für das? Gibt es eine Bibliothek-Klasse, die bereits T / SQL-Ausdrücke aufbaut, vorzugsweise in C ++ geschrieben.

Langsamste Sache über Strings ist malloc. Es dauert 4 KB pro Zeichenfolge auf 32-Bit-Plattformen. Betrachten wir die Optimierung Anzahl der String-Objekte erstellt.

Wenn Sie C # verwenden müssen, würde ich so etwas wie dies empfehlen:

string varString1 = tableName;
string varString2 = tableName;

StringBuilder sb1 = new StringBuilder("const expression");
sb1.Append(varString1);

StringBuilder sb2 = new StringBuilder("const expression");
sb2.Append(varString2);

string resultingString = sb1.ToString() + sb2.ToString();

Ich würde sogar so weit, lassen Sie den Computer auswerten den besten Weg für Objektinstanziierung mit Dependency Injection-Frameworks gehen, wenn perf, die wichtig ist.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top