一个 温暖的 在一个寒冷的冬天晚上,我想在办公室里热身并测试一个同事的理论,即C ++向量可以比CPython列表更快地完成任务。
在该公司,我们正在开发基于Django的产品,并且碰巧需要处理大量词典。 一位同事建议,使用C ++的实现会快得多,但是我并没有感到Guido和社区可能比我们在C语言中更酷,并且可能已经决定并绕过了所有陷阱,从而更快地实现了一切。
为了验证该理论,我决定编写一个小的测试文件,在其中决定循环运行,将相同内容的1M字典插入数组,并向量连续插入100次。
结果,虽然他们是预期的,但也是突然的。
碰巧我们正在积极使用Cython,因此,通常,在完全CPython实现中,结果会有所不同。
展位
- 计算Linux onegreyonewhite 4.18.14-计算#1 SMP PREEMPT星期六10月13日21:03:27 UTC 2018 x86_64Intel®Core(TM)i7-4770 CPU @ 3.40GHz纯正英特尔GNU / Linux
- Python 2.7和3.6
- Cython 0.28.3
- gcc(Gentoo 7.3.0-r3 p1.4)
剧本
顺便说一下,我不得不在这里修补。 为了获得最真实的数字(即不仅要对其进行超级优化,而且还可以使我们稍后使用它而无需铃鼓跳舞),我们必须在主脚本中进行所有操作,并将所有其他.h最小化。
第一个问题是vector的Cython包装器不希望这样工作:
出于所有这些,他们得到了一个错误,即无法将命令强制转换为PyObject。 当然,这些都是Cython问题,但是由于使用了它,因此需要解决此特定问题。
我不得不做一个小拐杖的形式
#include "Python.h" static PyObject * convert_to_pyobject(PyObject *obj) { return obj; }
最神奇的是,它确实有效。 最让我感到恐惧的是,我不完全理解后果的原因和后果。
最终来源cython_experiments.h
#include "Python.h" static PyObject * convert_to_pyobject(PyObject *obj) { return obj; }
cython_experiments.pyx
尝试1
我真的希望能够为项目收集* .whl,并且几乎所有系统上都包含* .whl,因此将优化标志首先设置为0。这导致了一个奇怪的结果:
Python 2.7 Statistics: attempts: 100 list avg time: 2.61709237576 vector avg time: 2.92562381506
经过一番思考,我决定我们仍然使用-O1标志,因此我将其设置为相同,并得到了它:
Python 2.7 Statistics: attempts: 100 list avg time: 2.49274396896 vector avg time: 0.922211170197
不知何故,我有点不高兴:不过,对Guido and Co.的专业精神的信念让我失望了。 但是后来,我注意到该脚本以某种方式可疑地占用了内存,到最后它占用了约20GB的RAM。 问题是这样的:在最终脚本中,您可以在传递循环之后观察free函数。 在这次迭代中,他还没有。 然后我想...
但是我可以禁用gc吗?
在两次尝试之间,我尝试了gc.disable() ,然后尝试了gc.enable() 。 我启动程序集和脚本并得到:
Python 2.7 Statistics: attempts: 100 list avg time: 1.00309731514 vector avg time: 0.941153049469
总的来说,相差不大,所以我认为没有意义 多付 尝试以某种方式变态并仅使用CPython,但仍需使用Cython收集它。
可能很多人有一个问题:“记忆有什么?” 最令人惊讶的(不)是什么都没有。 她以相同的速度和数量增长。 我想到了一篇文章 ,但我根本不想进入Python源。 是的,这仅意味着一件事-实现向量的问题。
决赛
经过大量的类型转换折磨之后,即,向量接受了指向字典的指针,获得了相同的结果脚本,并且在gc打开的情况下,我得到了平均2.6倍的差异(向量更快)并且具有相对较好的内存性能。
突然间,我意识到我只在Py2.7下收集所有东西,甚至没有尝试对3.6做任何事情。
在这里,我真的感到很惊讶(在得出之前的结果之后,这种惊讶是合乎逻辑的):
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
有了所有这些,gc仍然可以工作,内存没有吞噬,并且它是相同的脚本。 意识到在一年多一点之后,有必要告别2.7,我仍然想知道它们之间是否存在如此大的差异。 最常见的是,我听到/阅读/进行了实验,Py3.6比Py2.7慢。 但是,来自Cython开发人员的家伙做了不可思议的事情,改变了形势。
总结
经过这个实验,我们决定不花太多时间来支持Python 2.7并重新构建C ++应用程序的任何部分,只是因为这样做不值得。 一切都已经写在我们面前,我们只能正确地使用它来解决特定的问题。
UPD 12/24/2018:
在iCpu的建议下,并且经过侧面攻击之后,检查不了解是什么以及如何做,我尝试以将来最方便的开发方式重写C ++部分,并尽量减少抽象。 结果更糟:
缺乏C ++知识的结果cython_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
有什么想法可以在比较器中进行改进以使其更快地工作吗?