{"id":1052,"date":"2016-10-26T20:00:19","date_gmt":"2016-10-26T18:00:19","guid":{"rendered":"http:\/\/morony.pl\/?p=1052"},"modified":"2016-10-26T00:07:14","modified_gmt":"2016-10-25T22:07:14","slug":"czy-python-moze-byc-876-razy-szybszy","status":"publish","type":"post","link":"https:\/\/morony.pl\/?p=1052","title":{"rendered":"Czy Python mo\u017ce by\u0107 876 razy szybszy?"},"content":{"rendered":"<p>Pythona zacz\u0105\u0142em u\u017cywa\u0107 jakie\u015b 5 lat temu. Na pocz\u0105tku nie by\u0142em przekonany, ale nie mia\u0142em wyboru. Z czasem si\u0119 przekona\u0142em i zrobi\u0142em kilka projekt\u00f3w ma\u0142ych i du\u017cych. Dodatkowo uczy\u0142em podstaw programowania w pythonie przez 4 lata &#8211; do tej pory nie jestem przekonany czy jest to dobry j\u0119zyk na pocz\u0105tek (troch\u0119 za du\u017co wybacza), ale nie by\u0142a to moja decyzja. <\/p>\n<p>Wracaj\u0105c do tematu: python ma jedn\u0105 powa\u017cn\u0105 wad\u0119 &#8211; jest strasznie wolny. Dzi\u015b chc\u0119 wam pokaza\u0107 na przyk\u0142adzie, jak w prosty spos\u00f3b mo\u017cna u\u017cy\u0107 funkcji napisanych w C\/C++ do przeprowadzania oblicze\u0144 w programie w napisanym w pythonie. Python jest rewelacyjny je\u015bli chodzi o wczytywanie, zapisywanie i wizualizacj\u0119 danych, z kolei C\/C++ jest bardzo wydajny. Po\u0142\u0105czenie wydaj\u0119 si\u0119 idealne i okazuje si\u0119 ca\u0142kiem proste. <\/p>\n<p>Dla przyk\u0142adu we\u017amy proste zadanie: generujemy list\u0119 N liczb losowych z zakresu od 0 do 1. Chcemy obliczy\u0107 wszystkie mo\u017cliwe sumy 3 liczb z tej listy bez powt\u00f3rze\u0144 i policzy\u0107 ile z nich wynosi 1 z pewn\u0105 ustalon\u0105 dok\u0142adno\u015bci\u0105. Oczywi\u015bcie mo\u017cna ten problem optymalizowa\u0107, ale dzi\u015b nie o tym. Chcemy por\u00f3wna\u0107 wydajno\u015b\u0107 czystego pythona i pythona wspieranego przez C++. <\/p>\n<p>W pierwszej kolejno\u015bci napiszmy sobie funkcj\u0119 w C++ (plik CALC.cpp):<br \/>\n<center><\/p>\n<pre style=\"width:700px; overflow:auto; text-align:left;\"><code class=\"c++\">#include \"CALC.h\"\r\n#include <cstdio>\r\n#include <cstdlib>\r\n#include <cmath>\r\n\r\nvoid _CALC(double *DATA, int *RESULT, int N,  double treshold)\r\n{\r\n\tprintf(\"C++ start\\n\");\r\n\r\n\tRESULT[0] = 0;\r\n\tfor (int a = 0; a < N; a++)\r\n\t{\r\n\t\tfor (int b = a+1; b < N; b++)\r\n\t\t{\r\n\t\t\tfor (int c = b+1; c < N; c++)\r\n\t\t\t{\r\n\t\t\t\tif (fabs(DATA[a] + DATA[b] + DATA[c] - 1) < treshold)\r\n\t\t\t\t{\r\n\t\t\t\t\tRESULT[0]++;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprintf(\"C++ finish\\n\");\r\n}\r\n<\/code><\/pre>\n<p><\/center><\/p>\n<p>W parze mamy plik nag\u0142\u00f3wkowy (CALC.h):<br \/>\n<center><\/p>\n<pre style=\"width:700px; overflow:auto; text-align:left;\"><code class=\"c++\">void _CALC(double *DATA, int *RESULT, int N, double treshold);\r\n<\/code><\/pre>\n<p><\/center><\/p>\n<p>Powy\u017csza funkcja robi dok\u0142adnie to co za\u0142o\u017cyli\u015bmy - zlicza ile kombinacji bez powt\u00f3rze\u0144 daje sum\u0119 1 z dok\u0142adno\u015bci\u0105 \"treshold\". Za chwil\u0119 powinno by\u0107 jasne, czym s\u0105 argumenty tej funkcji. <\/p>\n<p>Naszym celem jest mo\u017cliwo\u015b\u0107 wywo\u0142ania tej funkcji z poziomu pythona w taki spos\u00f3b (program.py):<\/p>\n<p><center><\/p>\n<pre style=\"width:700px; overflow:auto; text-align:left;\"><code class=\"python\">import numpy\r\nimport CALC\r\nimport time\r\nstart_time = time.time()\r\n\r\nN = 1000\r\ntreshold = 1e-7\r\n\r\nDATA = numpy.random.uniform(0, 1, size=N)\r\nRESULT = numpy.zeros((1,), dtype=numpy.int)\r\n\r\nstart_time = time.time()\r\nCALC.CALC(DATA, RESULT, N, treshold)\r\nt1 = (time.time() - start_time)\r\nprint(\"C++:\", RESULT[0], t1)\r\n\r\n\r\nstart_time = time.time()\r\ncount = 0;\r\nfor a in range(0,N):\r\n\tfor b in range(a+1,N):\r\n\t\tfor c in range(b+1,N):\r\n\t\t\tif abs(DATA[a]+DATA[b]+DATA[c]-1) < treshold:\r\n\t\t\t\tcount =count + 1\r\nt2 = (time.time() - start_time)\r\nprint(\"Python:\", count, t2)\r\n\r\nprint(\"Speed:\", t2\/t1)\r\n<\/code><\/pre>\n<p><\/center><\/p>\n<p>Dok\u0142adnie chodzi o lini\u0119 <\/p>\n<p><center><\/p>\n<pre style=\"width:700px; overflow:auto; text-align:left;\"><code class=\"python\">CALC.CALC(DATA, RESULT, N, treshold)\r\n<\/code><\/pre>\n<p><\/center><\/p>\n<p>Potrzebujemy powi\u0105zania (interfejsu) pomi\u0119dzy pythonem i C++ (CALCmodule.cpp). Jest to kawa\u0142ek kody, kt\u00f3ry z naszej funkcji C++ pozwala zrobi\u0107 modu\u0142 dla pythona. Nie pisa\u0142em tego sam, z\u0142o\u017cy\u0142em z kilku tutoriali. Najwa\u017cniejsza jest funkcja CALC, gdzie definiujemy nasze zmienne i wywo\u0142ujemy funkcj\u0119 licz\u0105c\u0105:<\/p>\n<p><center><\/p>\n<pre style=\"width:700px; height:500px; overflow:auto; text-align:left;\"><code class=\"c++\">#include <Python.h>\r\n#define NPY_NO_DEPRECATED_API NPY_1_10_API_VERSION\r\n#include <numpy\/arrayobject.h>\r\n#include \"CALC.h\"\r\n\r\n\r\n\r\nstruct module_state {\r\n    PyObject *error;\r\n};\r\n\r\n#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m))\r\n\r\nstatic PyObject* CALC(PyObject* self, PyObject* args)\r\n{\r\n\tPyArrayObject *data_obj;\r\n\tPyArrayObject *result_obj;\r\n\tdouble treshold;\r\n\tint N;\r\n\r\n\tif (!PyArg_ParseTuple(args, \"OOid\", &data_obj, &result_obj, &N, &treshold))\r\n\t{\r\n\t\tPy_INCREF(Py_None);\r\n\t\treturn Py_None;\r\n\t}\r\n\r\n\tdouble *DATA = static_cast<double *>(PyArray_DATA(data_obj));\r\n\tint *RESULT = static_cast<int *>(PyArray_DATA(result_obj));\r\n\r\n\t_CALC(DATA, RESULT, N, treshold);\r\n\r\n\tPy_INCREF(Py_None);\r\n\treturn Py_None;\r\n}\r\n\r\nstatic PyMethodDef CALCMethods[] = {\r\n    {\"CALC\", CALC, METH_VARARGS, \"...\"},\r\n    {NULL, NULL, 0, NULL}\r\n};\r\n\r\nstatic int CALC_traverse(PyObject *m, visitproc visit, void *arg) {\r\n    Py_VISIT(GETSTATE(m)->error);\r\n    return 0;\r\n}\r\n\r\nstatic int CALC_clear(PyObject *m) {\r\n    Py_CLEAR(GETSTATE(m)->error);\r\n    return 0;\r\n}\r\n\r\n\r\nstatic struct PyModuleDef moduledef = {\r\n        PyModuleDef_HEAD_INIT,\r\n        \"CALC\",\r\n        NULL,\r\n        sizeof(struct module_state),\r\n        CALCMethods,\r\n        NULL,\r\n        CALC_traverse,\r\n        CALC_clear,\r\n        NULL\r\n};\r\n\r\nextern \"C\" PyObject * PyInit_CALC(void)\r\n{\r\n\tPyObject *module = PyModule_Create(&moduledef);\r\n\tif (module == NULL)\r\n        return NULL;\r\n    struct module_state *st = GETSTATE(module);\r\n\r\n    st->error = PyErr_NewException(\"CALC.Error\", NULL, NULL);\r\n    if (st->error == NULL) \r\n    {\r\n        Py_DECREF(module);\r\n        return NULL;\r\n    }\r\n\timport_array();\r\n\tPy_INCREF(module);\r\n    return module;\r\n\r\n}\r\n<\/code><\/pre>\n<p><\/center><\/p>\n<p>Na stronie <a href=\"https:\/\/docs.python.org\/2\/c-api\/arg.html\">https:\/\/docs.python.org\/2\/c-api\/arg.html<\/a> znajduj\u0105 si\u0119 dok\u0142adnie opisy typ\u00f3w danych kt\u00f3re mo\u017cemy przekazywa\u0107 z pythona do C++ i z powrotem. W powy\u017cszym przyk\u0142adzie \"OOid\" oznacza dwa obiekty, liczb\u0119 ca\u0142kowit\u0105 i zmiennoprzecinkow\u0105 (double). Obiektem mo\u017ce by\u0107 lista, z kt\u00f3rej mo\u017cemy czyta\u0107 i do kt\u00f3rej pisa\u0107. Nazwa naszego modu\u0142u pojawia si\u0119 w tym kodzie wielokrotnie - zmieniaj\u0105c trzeba pami\u0119ta\u0107 o wszystkich miejscach. <\/p>\n<p>Ostatnim krokiem jest kompilacja modu\u0142u. Potrzebujemy skryptu do kompilacji (setup.py):<br \/>\n<center><\/p>\n<pre style=\"width:700px; overflow:auto; text-align:left;\"><code class=\"python\">from distutils.core import setup, Extension\r\nimport numpy.distutils.misc_util\r\nimport os\r\n\r\nos.environ[\"CC\"] = \"g++\"\r\nos.environ[\"CXX\"] = \"g++\"\r\n\r\nmodule1 = Extension('CALC', sources = ['CALCmodule.cpp', 'CALC.cpp'])\r\n\r\nsetup (name = 'CALC',\r\n       version = '1.0',\r\n       description = 'Package for calculating number sums',\r\n       ext_modules = [module1],\r\n\t   include_dirs=numpy.distutils.misc_util.get_numpy_include_dirs())\r\n\r\n<\/code><\/pre>\n<p><\/center><\/p>\n<p>Teraz mo\u017cemy kompilowa\u0107 i uruchamia\u0107:<br \/>\n<center><a href=\"http:\/\/morony.pl\/wp-content\/uploads\/2016\/10\/screenshot_35.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"682\" src=\"http:\/\/morony.pl\/wp-content\/uploads\/2016\/10\/screenshot_35-1024x682.png\" alt=\"screenshot_35\" style=\"width:400px;height:auto;\" class=\"alignnone size-large wp-image-1061\" srcset=\"https:\/\/morony.pl\/wp-content\/uploads\/2016\/10\/screenshot_35-1024x682.png 1024w, https:\/\/morony.pl\/wp-content\/uploads\/2016\/10\/screenshot_35-300x200.png 300w, https:\/\/morony.pl\/wp-content\/uploads\/2016\/10\/screenshot_35-768x511.png 768w, https:\/\/morony.pl\/wp-content\/uploads\/2016\/10\/screenshot_35.png 1226w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><\/center><\/p>\n<p>Funkcja w C++ potrzebowa\u0142a 0.12 sekundy na policzenie, \u017ce jest 19 tr\u00f3jek, kt\u00f3re z dok\u0142adno\u015bci\u0105 0.0000001 daj\u0105 sum\u0119 1. Python potrzebowa\u0142 na analogiczne liczenie 108.42 sekund: 876 razy d\u0142u\u017cej! Oczywi\u015bcie mo\u017cna by ten kod zoptymalizowa\u0107, ale por\u00f3wnujemy dwa identyczne rozwi\u0105zania. R\u00f3\u017cnica w czasie jest kolosalna. W mojej pracy cz\u0119sto spotykam si\u0119 z obliczeniami numerycznymi, dlatego w pierwszej kolejno\u015bci przygotowuj\u0119 prototyp w pythonie i fragment obliczeniowy przepisuj\u0119 na C++. <\/p>\n<p>Nie chc\u0119 wnika\u0107 w teori\u0119 budowania modu\u0142\u00f3w dla pythona bo si\u0119 na tym nie znam. Sam najlepiej ucz\u0119 si\u0119 na przyk\u0142adach, wi\u0119c mam nadziej\u0119, \u017ce ten Wam si\u0119 przyda. <\/p>\n<p>Dajcie zna\u0107, czy kiedy\u015b u\u017cywali\u015bcie takich rozwi\u0105za\u0144, albo czy przydadz\u0105 Wam si\u0119 w przysz\u0142o\u015bci. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>Pythona zacz\u0105\u0142em u\u017cywa\u0107 jakie\u015b 5 lat temu. Na pocz\u0105tku nie by\u0142em przekonany, ale nie mia\u0142em wyboru. Z czasem si\u0119 przekona\u0142em i zrobi\u0142em kilka projekt\u00f3w ma\u0142ych i du\u017cych. Dodatkowo uczy\u0142em podstaw programowania w pythonie przez 4 lata &#8211; do tej pory nie jestem przekonany czy jest to dobry j\u0119zyk na pocz\u0105tek (troch\u0119 za du\u017co wybacza), ale [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"_links":{"self":[{"href":"https:\/\/morony.pl\/index.php?rest_route=\/wp\/v2\/posts\/1052"}],"collection":[{"href":"https:\/\/morony.pl\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/morony.pl\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/morony.pl\/index.php?rest_route=\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/morony.pl\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1052"}],"version-history":[{"count":11,"href":"https:\/\/morony.pl\/index.php?rest_route=\/wp\/v2\/posts\/1052\/revisions"}],"predecessor-version":[{"id":1064,"href":"https:\/\/morony.pl\/index.php?rest_route=\/wp\/v2\/posts\/1052\/revisions\/1064"}],"wp:attachment":[{"href":"https:\/\/morony.pl\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1052"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/morony.pl\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1052"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/morony.pl\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1052"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}