Eins warm An einem kalten Winterabend wollte ich mich im Büro aufwärmen und die Theorie eines Kollegen überprüfen, dass der C ++ - Vektor die Aufgabe schneller bewältigen kann als die CPython-Liste.
Im Unternehmen entwickeln wir Produkte, die auf Django basieren, und so kam es, dass eine große Anzahl von Wörterbüchern verarbeitet werden musste. Ein Kollege schlug vor, dass die Implementierung in C ++ viel schneller sein würde, aber ich habe nicht das Gefühl verloren, dass Guido und die Community wahrscheinlich etwas cooler sind als wir in C und wahrscheinlich bereits alle Fallstricke entschieden und umgangen haben und alles viel schneller implementiert haben.
Um die Theorie zu testen, habe ich beschlossen, eine kleine Testdatei zu schreiben, in der ich beschlossen habe, das Einfügen von 1 Million Wörterbüchern desselben Inhalts in das Array und den Vektor 100 Mal hintereinander in einer Schleife auszuführen.
Die Ergebnisse waren zwar zu erwarten, aber auch plötzlich.
Es ist einfach so passiert, dass wir Cython aktiv verwenden, sodass sich die Ergebnisse bei einer vollständigen CPython-Implementierung im Allgemeinen unterscheiden.
Stehen
- Berechnen Sie Linux onegreyonewhite 4.18.14-berechne # 1 SMP PREEMPT Sa Okt 13 21:03:27 UTC 2018 x86_64 Intel® Core (TM) i7-4770 CPU bei 3,40 GHz GenuineIntel GNU / Linux
- Python 2.7 und 3.6
- Cython 0,28,3
- gcc (Gentoo 7.3.0-r3 p1.4)
Skript
Übrigens musste ich hier basteln. Um die realistischsten Zahlen zu erhalten (d. H. Nicht nur superoptimiert zu machen, sondern auch, damit wir sie später verwenden können, ohne mit einem Tamburin zu tanzen), mussten wir alles im Hauptskript tun und alle zusätzlichen .h minimieren.
Das erste Problem war, dass der Cython-Wrapper für Vektor nicht so funktionieren möchte:
Trotz alledem haben sie den Fehler erhalten, dass es unmöglich ist, PyObject ein Diktat zu geben. Natürlich sind dies Cython-Probleme, aber da wir es verwenden, müssen wir dieses spezifische Problem lösen.
Ich musste eine kleine Krücke in Form von machen
#include "Python.h" static PyObject * convert_to_pyobject(PyObject *obj) { return obj; }
Das Erstaunlichste ist, dass es funktioniert hat. Was mich am meisten erschreckt, ist, dass ich nicht ganz verstehe, warum und was die Konsequenzen haben.
Letzte Quellencython_experiments.h
#include "Python.h" static PyObject * convert_to_pyobject(PyObject *obj) { return obj; }
cython_experiments.pyx
Versuch 1
Ich möchte wirklich in der Lage sein, * .whl für das Projekt zu sammeln und dass alles auf fast jedem System landete, daher wurde das Optimierungsflag zuerst auf 0 gesetzt. Dies führte zu einem seltsamen Ergebnis:
Python 2.7 Statistics: attempts: 100 list avg time: 2.61709237576 vector avg time: 2.92562381506
Nach einigem Nachdenken entschied ich, dass wir immer noch das Flag -O1 verwenden, also setzte ich es trotzdem und bekam es:
Python 2.7 Statistics: attempts: 100 list avg time: 2.49274396896 vector avg time: 0.922211170197
Irgendwie war ich ein bisschen verärgert: Trotzdem ließ mich der Glaube an die Professionalität von Guido und Co. im Stich. Aber dann bemerkte ich, dass das Skript irgendwie verdächtig Speicher verbraucht und am Ende ungefähr 20 GB RAM verbraucht. Das Problem war folgendes: Im endgültigen Skript können Sie die freie Funktion nach dem Übergeben der Schleife beobachten. Bei dieser Iteration war er noch nicht. Dann dachte ich ...
Aber kann ich gc deaktivieren?
Zwischen den Versuchen habe ich gc.disable () und nach dem Versuch von gc.enable () gemacht . Ich starte die Assembly und das Skript und erhalte:
Python 2.7 Statistics: attempts: 100 list avg time: 1.00309731514 vector avg time: 0.941153049469
Im Allgemeinen ist der Unterschied nicht groß, also dachte ich, dass es keinen Sinn macht Überzahlung versuche irgendwie zu pervertieren und benutze einfach CPython, sammle es aber trotzdem mit Cython.
Wahrscheinlich haben viele eine Frage: "Was ist da mit der Erinnerung?" Das Erstaunlichste (nein) ist, dass nichts. Sie wuchs mit der gleichen Geschwindigkeit und in der gleichen Menge. Ein Artikel kam mir in den Sinn, aber ich wollte überhaupt nicht auf die Python-Quellen eingehen. Ja, und dies bedeutete nur eines - das Problem bei der Implementierung des Vektors.
Finale
Nach vielen Qualen bei der Typkonvertierung, nämlich dass der Vektor einen Zeiger auf ein Wörterbuch nehmen würde, wurde das gleiche resultierende Skript erhalten, und bei eingeschaltetem gc erhielt ich einen durchschnittlich 2,6-fachen Unterschied (der Vektor ist schneller) und eine relativ gute Speicherleistung.
Plötzlich wurde mir klar, dass ich alles nur unter Py2.7 sammle und nicht einmal versuche, mit 3.6 etwas zu tun.
Und hier war ich wirklich überrascht (nach den vorherigen Ergebnissen war die Überraschung logisch):
Python 3.6 Statistics: attempts: 100 list avg time: 0.8771139788627624 vector avg time: 1.075702157020569 Python 2.7 Statistics: attempts: 100 list avg time: 2.61709237576 vector avg time: 0.92562381506
Mit all dem funktionierte gc immer noch, der Speicher verschlang nicht und es war das gleiche Skript. Als ich merkte, dass es nach etwas mehr als einem Jahr notwendig sein würde, sich von 2.7 zu verabschieden, fragte ich mich immer noch, dass es einen solchen Unterschied zwischen ihnen gab. Am häufigsten hörte / las / experimentierte ich und Py3.6 war langsamer als Py2.7. Die Jungs von Cython-Entwicklern haben jedoch etwas Unglaubliches getan und die Situation im Keim verändert.
Zusammenfassung
Nach diesem Experiment haben wir beschlossen, uns nicht viel mit der Unterstützung von Python 2.7 zu beschäftigen und Teile von C ++ - Anwendungen neu zu erstellen, einfach weil es sich nicht lohnt. Alles wurde bereits vor uns geschrieben, wir können es nur richtig verwenden, um ein bestimmtes Problem zu lösen.
UPD 24.12.2008:
Auf Anraten von iCpu und nach Angriffen auf die Seite wird überprüft, um nicht zu verstehen, was und wie. Ich habe versucht, den C ++ - Teil auf die für die zukünftige Entwicklung bequemste Weise neu zu schreiben und Abstraktionen zu minimieren. Es stellte sich noch schlimmer heraus:
Das Ergebnis schlechter C ++ - Kenntnissecython_experiments.h
#include "Python.h" #include <vector> #include <algorithm> #ifndef PyString_AsString #define PyString_AsString PyUnicode_AsUTF8 #define PyString_FromString PyUnicode_FromString #endif typedef struct { char* name; bool reverse; } sortFiled; class cmpclass { public: cmpclass(std::vector<char*> fields) { for (std::vector<char*>::iterator it = fields.begin() ; it < fields.end(); it++){ bool is_reverse = false; char* name; if (it[0] == "-"){ is_reverse = true; for(int i=1; i<strlen(*it); ++i) name[i] = *it[i]; } else { name = *it; } sortFiled field = {name, is_reverse}; this->fields_to_cmp.push_back(field); } } ~cmpclass() { this->fields_to_cmp.clear(); this->fields_to_cmp.shrink_to_fit(); } bool operator() (PyObject* left, PyObject* right) { // bool result = false; for (std::vector<sortFiled>::iterator it = this->fields_to_cmp.begin() ; it < this->fields_to_cmp.end(); it++){ // PyObject* str_name = PyString_FromString(it->name); PyObject* right_value = PyDict_GetItem(right, str_name); PyObject* left_value = PyDict_GetItem(left, str_name); if(!it->reverse){ result = left_value < right_value; } else { result = (left_value > right_value); } PyObject_Free(str_name); if(!result) return false; } return true; } private: std::vector<sortFiled> fields_to_cmp; }; void vector_multikeysort(std::vector<PyObject *> items, PyObject* columns, bool reverse) { std::vector<char *> _columns; for (int i=0; i<PyList_GET_SIZE(columns); ++i) { PyObject* item = PyList_GetItem(columns, i); char* item_str = PyString_AsString(item); _columns.push_back(item_str); } cmpclass cmp_obj(_columns); std::sort(items.begin(), items.end(), cmp_obj); if(reverse) std::reverse(items.begin(), items.end()); } std::vector<PyObject *> _test_vector(PyObject* store_data_list, PyObject* columns, bool reverse = false) { int range_attempts = PyList_GET_SIZE(store_data_list); std::vector<PyObject *> data_list; for (int i=0; i<range_attempts; ++i) { data_list.push_back(PyList_GetItem(store_data_list, i)); } vector_multikeysort(data_list, columns, reverse); return data_list; }
cython_experiments.pyx
Python 3.6 Statistics: attempts: 10 list avg time: 0.2640914678573608 vector avg time: 2.5774293661117555
Irgendwelche Ideen, was im Coparator verbessert werden könnte, damit es schneller funktioniert?