Satu hangat Pada malam musim dingin yang dingin, saya ingin melakukan pemanasan di kantor dan menguji teori dari seorang kolega bahwa vektor C ++ dapat melakukan tugas lebih cepat daripada daftar CPython.
Di perusahaan, kami sedang mengembangkan produk-produk yang berbasis pada Django, dan kebetulan bahwa perlu untuk memproses sejumlah besar kamus. Seorang kolega menyarankan bahwa implementasi di C ++ akan jauh lebih cepat, tetapi saya tidak kehilangan perasaan bahwa Guido dan komunitas mungkin sedikit lebih keren daripada kami di C dan mungkin telah memutuskan dan melewati semua jebakan, mengimplementasikan semuanya lebih cepat.
Untuk menguji teorinya, saya memutuskan untuk menulis file tes kecil di mana saya memutuskan untuk menjalankan dalam lingkaran penyisipan kamus 1M dari konten yang sama ke dalam array dan vektor 100 kali berturut-turut.
Hasilnya, meskipun mereka diharapkan, tetapi juga tiba-tiba.
Kebetulan kami aktif menggunakan Cython, jadi secara umum hasilnya akan berbeda pada implementasi CPython sepenuhnya.
Berdiri
- Hitung Linux onegreyonewhite 4.18.14-menghitung # 1 SMP PREEMPT Sabtu 13 Oktober 21:03:27 UTC 2018 x86_64 Intelยฎ Core (TM) i7-4770 CPU @ 3.40GHz GenuineIntel GNU / Linux
- Python 2.7 dan 3.6
- Cython 0.28.3
- gcc (Gentoo 7.3.0-r3 hal1.4)
Skrip
Ngomong-ngomong, aku harus bermain-main di sini. Untuk mendapatkan angka yang paling realistis (mis. Tidak hanya membuatnya super optimal, tetapi juga agar kami dapat menggunakannya nanti tanpa menari dengan rebana), kami harus melakukan segalanya dalam skrip utama, dan meminimalkan semua tambahan .h .
Masalah pertama adalah bahwa pembungkus Cython untuk vektor tidak ingin bekerja seperti ini:
Untuk semua ini, mereka mendapat kesalahan bahwa tidak mungkin untuk melemparkan dict ke PyObject. Tentu saja, ini adalah masalah Cython, tetapi karena kita menggunakannya, kita perlu menyelesaikan masalah khusus ini.
Saya harus membuat kruk kecil dalam bentuk
#include "Python.h" static PyObject * convert_to_pyobject(PyObject *obj) { return obj; }
Hal yang paling menakjubkan adalah itu berhasil. Yang paling membuatku takut adalah aku tidak sepenuhnya mengerti mengapa dan apa akibatnya.
Sumber Akhircython_experiments.h
#include "Python.h" static PyObject * convert_to_pyobject(PyObject *obj) { return obj; }
cython_experiments.pyx
Percobaan 1
Saya benar-benar ingin dapat mengumpulkan * .whl untuk proyek dan itu semua berakhir di hampir semua sistem, jadi bendera pengoptimalan pertama kali ditetapkan ke 0. Ini menghasilkan hasil yang aneh:
Python 2.7 Statistics: attempts: 100 list avg time: 2.61709237576 vector avg time: 2.92562381506
Setelah sedikit refleksi, saya memutuskan bahwa kami masih menggunakan flag -O1, jadi saya mengatur semuanya sama dan mendapatkannya:
Python 2.7 Statistics: attempts: 100 list avg time: 2.49274396896 vector avg time: 0.922211170197
Entah bagaimana, saya sedikit kesal: kepercayaan terhadap profesionalisme Guido and Co. mengecewakan saya. Tapi kemudian, saya perhatikan bahwa skripnya entah bagaimana mencurigakan memakan memori dan pada akhirnya itu memakan sekitar 20GB RAM. Masalahnya adalah ini: di skrip akhir, Anda dapat mengamati fungsi bebas, setelah melewati loop. Pada iterasi ini, dia belum. Lalu saya berpikir ...
Tetapi bisakah saya menonaktifkan gc?
Di antara upaya, saya membuat gc.disable () dan setelah mencoba gc.enable () . Saya memulai perakitan dan skrip dan mendapatkan:
Python 2.7 Statistics: attempts: 100 list avg time: 1.00309731514 vector avg time: 0.941153049469
Secara umum, perbedaannya tidak besar, jadi saya pikir tidak ada gunanya membayar lebih cobalah untuk memutarbalikkan entah bagaimana dan hanya menggunakan CPython, tetapi masih mengumpulkannya dengan Cython.
Mungkin banyak yang punya pertanyaan: "Ada apa dengan ingatan itu?" Yang paling menakjubkan (tidak) adalah apa-apa. Dia tumbuh pada tingkat yang sama dan dalam jumlah yang sama. Sebuah artikel muncul di benak saya, tetapi saya tidak mau masuk ke sumber Python sama sekali. Ya, dan ini berarti hanya satu hal - masalah dalam implementasi vektor.
Terakhir
Setelah banyak siksaan dengan konversi jenis, yaitu, sehingga vektor menerima pointer ke kamus, skrip yang dihasilkan sama diperoleh dan dengan gc dihidupkan saya mendapat perbedaan rata-rata 2,6 kali (vektor lebih cepat) dan kinerja memori yang relatif baik.
Tiba-tiba saya sadar bahwa saya mengumpulkan semuanya hanya di bawah Py2.7 dan bahkan tidak mencoba melakukan apa pun dengan 3.6.
Dan di sini saya sangat terkejut (setelah hasil sebelumnya, kejutan itu logis):
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
Dengan semua ini, gc masih berfungsi, memori tidak melahap, dan itu skrip yang sama. Menyadari bahwa setelah sedikit lebih dari setahun, akan perlu untuk mengucapkan selamat tinggal kepada 2.7, saya masih bertanya-tanya bahwa ada perbedaan seperti itu di antara mereka. Paling sering, saya mendengar / membaca / bereksperimen dan Py3.6 lebih lambat dari Py2.7. Namun, orang-orang dari pengembang Cython melakukan sesuatu yang luar biasa dan mengubah situasi sejak awal.
Ringkasan
Setelah percobaan ini, kami memutuskan untuk tidak repot-repot dengan dukungan untuk Python 2.7 dan membuat ulang setiap bagian dari aplikasi C ++, hanya karena itu tidak layak. Semuanya sudah ditulis sebelum kita, kita hanya bisa menggunakannya dengan benar untuk menyelesaikan masalah tertentu.
UPD 12/24/2018:
Atas saran iCpu dan setelah serangan ke samping, diperiksa untuk tidak memahami apa dan bagaimana, saya mencoba menulis ulang bagian C ++ dengan cara yang paling nyaman untuk pengembangan di masa depan, serta meminimalkan abstraksi. Ternyata lebih buruk lagi:
Hasil dari pengetahuan C ++ yang burukcython_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
Adakah ide apa yang bisa ditingkatkan di coparator sehingga bekerja lebih cepat?