2010-08-13 6 views
9

Unterstrings eines Strings zu nehmen ist eine sehr häufige Operation zur String-Manipulation, aber ich habe gehört, dass es erhebliche Unterschiede in der Leistung/Implementierung zwischen der Java- und .NET-Plattform geben kann. Insbesondere hörte ich, dass in Java, java.lang.String bietet konstant Zeitbetrieb für substring, aber in .NET, System.String bietet lineare Leistung Substring.Vergleich der Leistung von Teilstringoperationen zwischen .NET und Java

Sind diese wirklich der Fall? Kann dies in der Dokumentation/im Quellcode etc. bestätigt werden? Ist diese Implementierung spezifisch oder spezifiziert durch die Sprache und/oder die Plattform? Was sind die Vor- und Nachteile jedes Ansatzes? Was sollte eine Person, die von einer Plattform zur anderen migriert, suchen, um zu verhindern, dass sie in Performance-Fallen gerät?

+1

Warum sollten Sie nicht Ihre eigenen Mikro-Benchmarks ausführen, um dies zu testen ? Können Sie zu Quellen verlinken, die sagen, dass sie "schlechte" Leistung haben? – Oded

+0

@Oded: Quelle ist Danny Chens Kommentar hier http://StackOverflow.com/Questions/3474254/How-to-make-a-first-letter-capital-in-c/3474263#3474263; Ehrlich gesagt würde ich erstaunt sein, wenn "Substring" nicht "O (1)" Zeit-und-Raum-Operation (wie beispielsweise Java) ist, aber ich gebe ihm den Vorteil des Zweifels, da ich .NET nicht kenne. – polygenelubricants

+1

Was bedeutet "schlechte Leistung"? Relativ zu was? .NET hat auch eine schlechte Leistung, wenn es beispielsweise mit C++ verglichen wird. Sollten wir deswegen .NET fallen lassen? –

Antwort

11

In .NET Substring ist O (n) anstelle des O (1) von Java. Der Grund dafür ist, dass in .NET das String-Objekt alle tatsächlichen Zeichendaten selbst enthält. - Um also einen Teilstring zu erhalten, müssen alle Daten innerhalb des neuen Teilstrings kopiert werden. In Java kann substring einfach ein neues Objekt erstellen, das sich auf das ursprüngliche char-Array bezieht, mit einem anderen Anfangsindex und einer anderen Länge.

Es gibt Vor- und Nachteile der einzelnen Ansätze:

  • .NET Ansatz besser Cache-Kohärenz hat, schafft weniger Objekte und vermeidet die Situation, in der eine kleine Teilkette verhindert, dass eine sehr große char[] wird Müll gesammelt . Ich glaube, dass es in manchen Fällen auch intern sehr einfach sein kann.
  • Java Ansatz macht einen Teil sehr effizient, zu nehmen und wahrscheinlich einige andere Operationen zu

Es ist ein wenig mehr Detail in meinem strings article.

Wie für die allgemeine Frage der Vermeidung von Performance-Fallstricke, ich denke, ich sollte eine vorbereitete Antwort zum Ausschneiden und Einfügen bereit haben: Stellen Sie sicher, ist effizient, und implementieren Sie es auf die lesbarste Weise, die Sie können. Messen Sie die Leistung und optimieren Sie, wo Sie Engpässe finden.


Übrigen macht diese string ganz besonderer - es ist das einzige nicht-Array-Typ, dessen Speicherbedarf variiert nach Instanz innerhalb desselben CLR.

Für kleine Saiten ist das ein großer Gewinn. Es ist schlimm genug, dass es den gesamten Overhead von ein Objekt gibt, aber wenn es auch ein zusätzliches Array gibt, könnte eine Zeichenfolge aus einem Zeichen etwa 36 Byte in Java benötigen. (Das ist eine "Finger-in-the-Air" -Nummer - ich kann mich nicht an die genauen Objektkosten erinnern. Es hängt auch von der verwendeten VM ab.)

2

Reflektor ist das, was man von Substring bekommen (Int32, Int32)

[SecuritySafeCritical, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] 
public string Substring(int startIndex, int length) 
{ 
    return this.InternalSubStringWithChecks(startIndex, length, false); 
} 

, wenn Sie innerhalb des letzten Anrufs auf dem Gehen halten, ist zu einem

internal static unsafe void wstrcpy(char* dmem, char* smem, int charCount) 

kopiert die Zeichen mithilfe von Zeigern. Der vollständige Code sieht wirklich groß aus, aber Sie werden nicht sehen, wie schnell oder langsam es ist, bis Sie es ausführen und es benchmarken.

0

Es hängt wirklich von Ihrer Arbeitsbelastung ab. Wenn Sie eine Schleife bilden und viele Teilstring-Aufrufe ausführen, liegt möglicherweise ein Problem vor. Für den SO Post, auf den du dich beziehst, bezweifle ich, dass es jemals ein Problem sein würde. Mit dieser Einstellung könnten Sie jedoch immer in einer Situation von "Tod durch tausend Papierschnitte" landen. In der SO buchen Sie beziehen sich auf, haben wir folgendes:

String after = before.Substring(0, 1).ToUpper() + before.Substring(1); 

den Compiler Unter der Annahme, nicht einige verrückte Optimierungen tun, wird dies mindestens vier neue Saiten (2 Substring Anrufe erstellen, einen ToUpper Anruf, und die Verkettung). Der Teilstring wird genau so implementiert, wie Sie es erwarten würden (String-Kopie), aber drei der oben zugewiesenen Strings werden schnell zu einem Abfall. Wenn Sie viel davon tun, entsteht unnötiger Speicherdruck. Ich sage "unnötig", weil Sie wahrscheinlich mit etwas mehr Zeitaufwand eine wirtschaftlichere Lösung finden können.

Am Ende der Profiler ist dein bester Freund :)