Dies ist ein (langer) Blog, der unsere Erfahrungen bei der Migration eines großen Teils des Python/Cython-Codes in die Go-Sprache dokumentiert. Wenn Sie die ganze Geschichte und die Hintergründe erfahren möchten, dann lesen Sie weiter. Wenn Sie nur daran interessiert sind, was Python-Entwickler wissen müssen, bevor sie einsteigen, klicken Sie auf den Link unten:
Tipps und Tricks für die Migration von Python zu Go
Hintergrund
Unser größter Erfolg in der Repustate-Technologie ist die Realisierung der arabischen Stimmungsanalyse. Arabisch ist wirklich eine harte Nuss, die grammatikalischen Formen der Wörter sind zu komplex. Die Tokenisierung (das Aufteilen eines Satzes in unabhängige Wörter) ist im Arabischen schwieriger als beispielsweise im Englischen, da arabische Wörter Leerzeichen enthalten können (z. B. innerhalb von Aleph-Positionen). Dies muss nicht geheim gehalten werden, das heißt, Repustate verwendet eine Support Vector Machine (SVM), um die wahrscheinlichste Bedeutung des Satzes zu ermitteln, und analysiert dann die Stimmung auf dieser Grundlage. Wir haben insgesamt 22 Modelle (22 Support-Vektor-Maschinen) verwendet und jedes Wort im Dokument analysiert. Mit anderen Worten: Wenn ein Dokument 500 Wörter enthält, gibt es mehr als 10.000 Support-Vector-Machine-Vergleichsoperationen.
Python
Repustate ist fast vollständig in Python implementiert, da wir Django als Anwendungsprogrammierschnittstelle und Website-Architektur verwenden. Daher können wir nur die Einheit des Codes aufrechterhalten und die gesamte arabische Emotions-Engine in Python implementieren. Im Prototyping- und Implementierungsprozess ist Python immer noch sehr gut. Sehr starke Ausdrucksfähigkeit und leistungsstarke Bibliotheksressourcen von Drittanbietern. Es ist immer noch perfekt, wenn Sie nur Webseiten bereitstellen. Wenn Sie jedoch Berechnungen auf niedriger Ebene durchführen und viele Vergleichsoperationen an Hash-Tabellen (Wörterbüchern in Python) durchführen müssen, verlangsamt sich die Geschwindigkeit. Wir können nur 2 bis 3 arabische Dokumente pro Sekunde verarbeiten, was zu langsam ist. Vergleichen Sie dies mit unserer englischen Sentiment-Engine, die 500 Dokumente pro Sekunde verarbeiten kann.
Engpass
Also haben wir den Python-Profiler gestartet, um zu untersuchen, welcher Teil langsam ausgeführt wurde. Erinnern Sie sich, als ich sagte, wir würden für jedes Wort 22 Support-Vektor-Maschinen verwenden? Diese Prozesse sind alle seriell und es gibt keine parallelen Vorgänge. Okay, unsere erste Idee besteht darin, dies in eine Operation ähnlich wie Map/Reduce umzuwandeln. Lange Rede, kurzer Sinn: Map/Reduce passt nicht gut in Python. Python ist überhaupt nicht einfach zu verwenden, wenn Sie Parallelität benötigen. Auf der PyCon 2013 erwähnte Guido Tulip, sein neues Projekt, das dieses Problem lösen soll, aber es sollte noch eine Weile dauern, bis es gestartet wurde. Wenn es bereits eine bessere Option gibt, warum sollten wir dann darauf warten?
Go-Sprache ändern oder nach Hause gehen und farmen
Meine Freunde bei Mozilla sagten mir, dass der größte Teil des Codes für die Protokollierungsarchitektur im Mazilla-Dienst geändert wurde auf Go umgestellt, teilweise aufgrund der Leistungsfähigkeit von Goroutine (Go-Thread). Go wurde von einer Gruppe von Leuten bei Google entwickelt, um Parallelität als erstklassiges Konzept und nicht als nachträglichen Gedanken wie die verschiedenen Lösungen von Python zu betrachten. Also begannen wir, Python auf Go umzustellen.
Obwohl der Go-Code noch nicht auf Produktionsniveau ist, sind die Ergebnisse bereits sehr ermutigend. Wir erreichten 1000 Dokumente pro Sekunde, verbrauchten weniger Speicher und mussten uns nicht mit dem lästigen Multi-Process/gevent/„Warum hat Strg C meinen Prozess beendet?“-Code befassen, der mit Python geliefert wird.
Warum wir uns in Go verliebt haben
Jeder, der ein wenig darüber weiß, wie Programmiersprachen funktionieren (versteht den Unterschied zwischen Interpretation und Kompilierung und dynamisch und statisch) und sagen Sie: „Mann, Go ist offensichtlich schneller.“ Ja, wir könnten das Ganze auch in Java umschreiben und eine ähnliche Leistung erzielen, aber das ist nicht der Grund, warum Go gewinnt. Der Code, den Sie in Go schreiben, ist leicht korrekt. Ich kann nicht erklären, warum, aber sobald der Code kompiliert ist (die Kompilierungsgeschwindigkeit ist sehr hoch), werden Sie das Gefühl haben, dass er funktionieren kann (nicht nur ausgeführt werden kann, ohne einen Fehler auszulösen, sondern auch logisch korrekt). Ich weiß, das klingt seltsam, aber es ist wahr. Dies ist so, als würde Python das Redundanzproblem (oder keine Redundanz) lösen. Es behandelt Funktionen als Objekte der ersten Ebene, sodass eine funktionale Programmierung problemlos durchgeführt werden kann. Go-Threads und -Kanäle machen Ihr Leben so einfach. Sie profitieren außerdem von den Leistungsverbesserungen durch statische Eingabe und einer präziseren Steuerung der Speicherzuweisung, ohne dass die Ausdruckskraft verloren geht.
Dinge, die wir hätten wissen sollen
Trotz allem Lob erfordert die Arbeit mit Go eine andere Denkweise als die Arbeit mit Python. Im Folgenden sind einige Hinweise aufgeführt, die mir während der Migration zufällig in den Sinn kamen, als ich Python in Go konvertierte:
Es gibt keinen integrierten Sammlungstyp (Sie müssen Map verwenden und dann die Existenz überprüfen)
Da es keinen Sammlungstyp gibt, müssen Sie Schnitt-, Vereinigungs- und andere Methoden selbst implementieren
Es gibt kein Tupel (Tupel), Sie müssen Ihre eigene Struktur (Struktur) entwerfen oder Slice verwenden (ähnlich einem Array)
Keine Ähnlichkeit_ Die Methode _getattr_() erfordert, dass Sie die Existenz überprüfen, aber keinen Standardwert festlegen. In Python können Sie beispielsweise Folgendes schreiben: value = dict.get("a_key ", "default_value")
Muss auf Fehler überprüft werden (oder diese zumindest explizit ignorieren)
Kann keine ungenutzten Variablen und Pakete haben und muss von Zeit zu Zeit Code auskommentieren Zeit
Wechseln Sie zwischen []Byte und String, die reguläre Verarbeitung (regexp) verwendet []Byte (schreibbar). Das ist richtig, aber das Hin- und Herkonvertieren ist immer noch mühsam.
Die Python-Syntax ist entspannter. Sie können Indizes außerhalb des Bereichs verwenden, um Fragmente einer Zeichenfolge ohne Fehler abzurufen, oder Sie können negative Zahlen verwenden, um Fragmente abzurufen. Nicht so bei Go.
Datenstrukturen gemischter Typen können nicht verwendet werden. Das mag nicht unbedingt angemessen sein, aber in Python habe ich manchmal ein Wörterbuch, dessen Werte eine Mischung aus Strings und Listen sein können. Nicht in Go, Sie müssen die Datenstruktur oder benutzerdefinierte Struktur in Go bereinigen)
Camel-Fallkonvention (Funktionen/Strukturen, deren Anfangsbuchstabe nicht großgeschrieben wird, werden anderen Paketen nicht angezeigt). Ich bevorzuge Pythons Konvention für Kleinschreibung und Unterstrich.
Sie müssen explizit prüfen, ob der Fehler leer ist, im Gegensatz zu vielen Typen in Python, die wie boolesche Typen verwendet werden können (0, leere Zeichenfolge, Keiner kann als boolescher Wert „false“ verwendet werden)
Für einige Module (z. B. crypto/md5) ist die Dokumentation unzureichend, aber go-nutes im IRC ist sehr leistungsfähig und bietet starke Unterstützung
Number to string (int64->string) und []byte to string (As long). Da string([]byte)) anders ist, müssen Sie strconv
aufrufen, um Gos Code absolut wie eine Programmiersprache zu lesen, während Python wie Pseudocode geschrieben werden kann. Go verwendet mehr nicht-englische numerische Zeichen und verwendet || und && anstelle von or und and.
Das Schreiben von Dateien erfolgt über File.Write([]byte) und File.WriteString(string), was im Widerspruch zum Credo des Python-Entwicklers steht, das Problem nur auf eine Weise zu lösen.
Das Einfügen von Zeichenfolgen ist nicht einfach, Sie müssen häufig fmt.Sprintf verwenden
Es gibt keinen Konstruktor, die übliche Angewohnheit besteht darin, eine NewType()-Funktion zu schreiben, um die gewünschte Struktur zurückzugeben
Else (oder else if) muss korrekt formatiert sein und else muss in derselben Zeile stehen wie die geschweifte Klammer, die dem if entspricht. Fremdheit.
Verwenden Sie unterschiedliche Zuweisungsoperatoren innerhalb und außerhalb der Funktion = und := (Anmerkung des Übersetzers: Dies ist das Missverständnis des Autors, der Unterschied zwischen = und := ist ein explizit definierter Typ oder eine automatische Typableitung und außerhalb von Funktionsvariablen können nur verwendet werden =)
Wenn ich nur eine Liste von Schlüsseln (dict.keys()) oder Werten (dict.values()) oder eine Liste von Tupeln (dict.items () ), es gibt keine entsprechende Funktion in Go, Sie können
nur selbst iterieren