Einführung
Ich habe diesen Lieblingsalgorithmus, den ich vor einiger Zeit gemacht habe und den ich immer in neuen Programmiersprachen, Plattformen usw. schreibe und neu schreibe, als eine Art Benchmark. Obwohl meine Hauptprogrammiersprache C # ist, habe ich den Code buchstäblich kopiert und die Syntax leicht geändert, ihn in Java erstellt und festgestellt, dass er 1000x schneller ausgeführt wird.
Der Code
Es gibt ziemlich viel Code, aber ich werde nur diesen Ausschnitt präsentieren, der das Hauptproblem zu sein scheint:
for (int i = 0; i <= s1.Length; i++)
{
for (int j = i + 1; j <= s1.Length - i; j++)
{
string _s1 = s1.Substring(i, j);
if (tree.hasLeaf(_s1))
...
Die Daten
Es ist wichtig darauf hinzuweisen, dass die Zeichenfolge s1 in diesem speziellen Test eine Länge von 1 Million Zeichen (1 MB) hat.
Messungen
Ich habe meine Codeausführung in Visual Studio profiliert, weil ich dachte, dass die Art und Weise, wie ich meinen Baum erstelle oder wie ich ihn durchquere, nicht optimal ist. Nach Prüfung der Ergebnisse scheint die Leitung string _s1 = s1.Substring(i, j);
mehr als 90% der Ausführungszeit aufzunehmen!
Zusätzliche Beobachtungen
Ein weiterer Unterschied, den ich bemerkt habe, ist, dass Java, obwohl mein Code Single-Threaded ist, es schafft, ihn mit allen 8 Kernen (100% CPU-Auslastung) auszuführen, während mein C # -Code selbst mit Parallel.For () - und Multi-Threading-Techniken 35- schafft. Höchstens 40%. Da der Algorithmus linear mit der Anzahl der Kerne (und der Frequenz) skaliert, habe ich dies kompensiert und dennoch führt das Snippet in Java eine Größenordnung von 100-1000x schneller aus.
Argumentation
Ich gehe davon aus, dass der Grund dafür in der Tatsache liegt, dass Strings in C # unveränderlich sind, sodass String.Substring () eine Kopie erstellen muss. Da es sich um eine verschachtelte for-Schleife mit vielen Iterationen handelt, gehe ich davon aus, dass viel kopiert wird Die Speicherbereinigung wird fortgesetzt, ich weiß jedoch nicht, wie Substring in Java implementiert ist.
Frage
Welche Möglichkeiten habe ich derzeit? An der Anzahl und Länge der Teilzeichenfolgen führt kein Weg vorbei (dies ist bereits maximal optimiert). Gibt es eine Methode, die ich nicht kenne (oder vielleicht die Datenstruktur), die dieses Problem für mich lösen könnte?
Angeforderte minimale Implementierung (aus Kommentaren)
Ich habe die Implementierung des Suffixbaums ausgelassen, der im Aufbau O (n) und im Durchlauf O (log (n)) ist
public static double compute(string s1, string s2)
{
double score = 0.00;
suffixTree stree = new suffixTree(s2);
for (int i = 0; i <= s1.Length; i++)
{
int longest = 0;
for (int j = i + 1; j <= s1.Length - i; j++)
{
string _s1 = s1.Substring(i, j);
if (stree.has(_s1))
{
score += j - i;
longest = j - i;
}
else break;
};
i += longest;
};
return score;
}
Screenshot-Ausschnitt des Profilers
Beachten Sie, dass dies mit der Zeichenfolge s1 mit einer Größe von 300.000 Zeichen getestet wurde. Aus irgendeinem Grund werden 1 Million Zeichen in C # nie beendet, während es in Java nur 0,75 Sekunden dauert. Der verbrauchte Speicher und die Anzahl der Speicherbereinigungen scheinen kein Speicherproblem anzuzeigen. Der Peak betrug ungefähr 400 MB, aber angesichts des riesigen Suffixbaums scheint dies normal zu sein. Auch keine seltsamen Müllsammelmuster wurden entdeckt.
String
in Java ist auch unveränderlich. Hast du esStringBuilder
stattdessen versucht ?