{"cells": [{"cell_type": "markdown", "metadata": {}, "source": ["# Optimisation de code avec cffi, numba, cython\n", "\n", "L'id\u00e9e est de recoder une fonction en C. On prend comme exemple la fonction de pr\u00e9diction de la r\u00e9gression lin\u00e9aire de [scikit-learn](http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html) et de pr\u00e9voir le gain de temps qu'on obtient en recodant la fonction dans un langage plus rapide."]}, {"cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [{"data": {"text/html": ["
run previous cell, wait for 2 seconds
\n", ""], "text/plain": [""]}, "execution_count": 2, "metadata": {}, "output_type": "execute_result"}], "source": ["from jyquickhelper import add_notebook_menu\n", "add_notebook_menu()"]}, {"cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": ["memo_time = []\n", "import timeit\n", "\n", "def unit(x):\n", " if x >= 1: return \"%1.2f s\" % x\n", " elif x >= 1e-3: return \"%1.2f ms\" % (x* 1000)\n", " elif x >= 1e-6: return \"%1.2f \u00b5s\" % (x* 1000**2)\n", " elif x >= 1e-9: return \"%1.2f ns\" % (x* 1000**3)\n", " else:\n", " return \"%1.2g s\" % x\n", "\n", "def timeexe(legend, code, number=100, repeat=1000):\n", " rep = timeit.repeat(code, number=number, repeat=repeat, globals=globals())\n", " ave = sum(rep) / (number * repeat)\n", " std = (sum((x/number - ave)**2 for x in rep) / repeat)**0.5\n", " fir = rep[0]/number\n", " fir3 = sum(rep[:3]) / (3 * number)\n", " las3 = sum(rep[-3:]) / (3 * number)\n", " rep.sort()\n", " mini = rep[len(rep)//20] / number\n", " maxi = rep[-len(rep)//20] / number\n", " print(\"Moyenne: %s Ecart-type %s (with %d runs) in [%s, %s]\" % (\n", " unit(ave), unit(std), number, unit(mini), unit(maxi)))\n", " return dict(legend=legend, average=ave, deviation=std, first=fir, first3=fir3,\n", " last3=las3, repeat=repeat, min5=mini, max5=maxi, code=code, run=number)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## R\u00e9gression lin\u00e9aire"]}, {"cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": ["from sklearn.datasets import load_diabetes\n", "from sklearn.model_selection import train_test_split\n", "\n", "diabetes = load_diabetes()\n", "diabetes_X_train, diabetes_X_test, diabetes_y_train, diabetes_y_test = train_test_split(diabetes.data, diabetes.target)"]}, {"cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [{"data": {"text/plain": ["LinearRegression()"]}, "execution_count": 5, "metadata": {}, "output_type": "execute_result"}], "source": ["from sklearn.linear_model import LinearRegression\n", "clr = LinearRegression()\n", "clr.fit(diabetes_X_train, diabetes_y_train)"]}, {"cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([ -35.81159278, -267.39308261, 503.56121841, 337.87944184,\n", " -577.27255236, 373.62939477, -99.69779327, 78.39842094,\n", " 656.54309153, 80.3383998 ])"]}, "execution_count": 6, "metadata": {}, "output_type": "execute_result"}], "source": ["clr.coef_"]}, {"cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [{"data": {"text/plain": ["152.69613239933642"]}, "execution_count": 7, "metadata": {}, "output_type": "execute_result"}], "source": ["clr.intercept_"]}, {"cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 45.50 \u00b5s Ecart-type 6.34 \u00b5s (with 100 runs) in [40.87 \u00b5s, 52.95 \u00b5s]\n"]}], "source": ["z = diabetes_X_test[0:1,:]\n", "memo_time.append(timeexe(\"sklearn.predict\", \"clr.predict(z)\"))"]}, {"cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["45.2 \u00b5s \u00b1 744 ns per loop (mean \u00b1 std. dev. of 7 runs, 10,000 loops each)\n"]}], "source": ["%timeit clr.predict(z)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### optimisation avec cffi\n", "\n", "On s'inspire de l'exemple [Purely for performance (API level, out-of-line)](http://cffi.readthedocs.io/en/latest/overview.html?highlight=example#purely-for-performance-api-level-out-of-line)."]}, {"cell_type": "code", "execution_count": 9, "metadata": {"scrolled": false}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["generating .\\_linear_regression.c\n", "(already up-to-date)\n", "the current directory is 'C:\\\\xavierdupre\\\\__home_\\\\GitHub\\\\ensae_teaching_cs\\\\_doc\\\\notebooks\\\\2a'\n", "running build_ext\n", "building '_linear_regression' extension\n", "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\bin\\HostX86\\x64\\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -IC:\\Python395_x64\\include -IC:\\Python395_x64\\include -IC:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\ATLMFC\\include -IC:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\include -IC:\\Program Files (x86)\\Windows Kits\\NETFXSDK\\4.8\\include\\um -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\ucrt -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\shared -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\um -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\winrt -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\cppwinrt /Tc_linear_regression.c /Fo.\\Release\\_linear_regression.obj\n", "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\bin\\HostX86\\x64\\link.exe /nologo /INCREMENTAL:NO /LTCG /DLL /MANIFEST:EMBED,ID=2 /MANIFESTUAC:NO /LIBPATH:C:\\Python395_x64\\libs /LIBPATH:C:\\Python395_x64\\PCbuild\\amd64 /LIBPATH:C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\ATLMFC\\lib\\x64 /LIBPATH:C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\lib\\x64 /LIBPATH:C:\\Program Files (x86)\\Windows Kits\\NETFXSDK\\4.8\\lib\\um\\x64 /LIBPATH:C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.19041.0\\ucrt\\x64 /LIBPATH:C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.19041.0\\um\\x64 /EXPORT:PyInit__linear_regression .\\Release\\_linear_regression.obj /OUT:.\\_linear_regression.cp39-win_amd64.pyd /IMPLIB:.\\Release\\_linear_regression.cp39-win_amd64.lib\n"]}, {"data": {"text/plain": ["'C:\\\\xavierdupre\\\\__home_\\\\GitHub\\\\ensae_teaching_cs\\\\_doc\\\\notebooks\\\\2a\\\\_linear_regression.cp39-win_amd64.pyd'"]}, "execution_count": 10, "metadata": {}, "output_type": "execute_result"}], "source": ["from cffi import FFI\n", "ffibuilder = FFI()\n", "\n", "ffibuilder.cdef(\"int linreg(int, double *, double *, double, double *);\")\n", "\n", "ffibuilder.set_source(\"_linear_regression\",\n", "r\"\"\"\n", " static int linreg(int dimension, double * x, double *coef, double intercept, double * out)\n", " {\n", " for(; dimension > 0; --dimension, ++x, ++coef)\n", " intercept += *x * *coef;\n", " *out = intercept;\n", " return 1;\n", " }\n", "\"\"\")\n", "\n", "ffibuilder.compile(verbose=True)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["La fonction compil\u00e9e est accessible comme suit."]}, {"cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [{"data": {"text/plain": [""]}, "execution_count": 11, "metadata": {}, "output_type": "execute_result"}], "source": ["from _linear_regression import ffi, lib\n", "lib.linreg"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On s'inspire de l'exemple [How to pass a Numpy array into a cffi function and how to get one back out?](https://stackoverflow.com/questions/16276268/how-to-pass-a-numpy-array-into-a-cffi-function-and-how-to-get-one-back-out)."]}, {"cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": ["import numpy\n", "out = numpy.zeros(1)"]}, {"cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": ["ptr_coef = clr.coef_.__array_interface__['data'][0]\n", "cptr_coef = ffi.cast ( \"double*\" , ptr_coef )"]}, {"cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": ["x = diabetes_X_test[0:1,:]\n", "ptr_x = x.__array_interface__['data'][0]\n", "cptr_x = ffi.cast ( \"double*\" , ptr_x )"]}, {"cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": ["ptr_out = out.__array_interface__['data'][0]\n", "cptr_out = ffi.cast ( \"double*\" , ptr_out )"]}, {"cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [{"data": {"text/plain": ["1"]}, "execution_count": 16, "metadata": {}, "output_type": "execute_result"}], "source": ["n = len(clr.coef_)\n", "lib.linreg(n, cptr_x, cptr_coef, clr.intercept_, cptr_out)"]}, {"cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([214.72477745])"]}, "execution_count": 17, "metadata": {}, "output_type": "execute_result"}], "source": ["out"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On v\u00e9rifie qu'on obtient bien la m\u00eame chose."]}, {"cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([214.72477745])"]}, "execution_count": 18, "metadata": {}, "output_type": "execute_result"}], "source": ["clr.predict(x)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Et on mesure le temps d'ex\u00e9cution :"]}, {"cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 831.37 ns Ecart-type 708.08 ns (with 100 runs) in [416.00 ns, 1.52 \u00b5s]\n"]}], "source": ["memo_time.append(timeexe(\"cffi-linreg\", \"lib.linreg(n, cptr_x, cptr_coef, clr.intercept_, cptr_out)\"))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["C'est beaucoup plus rapide. Pour \u00eatre totalement honn\u00eate, il faut mesurer les \u00e9tapes qui consiste \u00e0 extraire les pointeurs."]}, {"cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([154.32457426])"]}, "execution_count": 20, "metadata": {}, "output_type": "execute_result"}], "source": ["def predict_clr(x, clr):\n", " out = numpy.zeros(1)\n", " ptr_coef = clr.coef_.__array_interface__['data'][0]\n", " cptr_coef = ffi.cast ( \"double*\" , ptr_coef )\n", " ptr_x = x.__array_interface__['data'][0]\n", " cptr_x = ffi.cast ( \"double*\" , ptr_x ) \n", " ptr_out = out.__array_interface__['data'][0]\n", " cptr_out = ffi.cast ( \"double*\" , ptr_out ) \n", " lib.linreg(len(x), cptr_x, cptr_coef, clr.intercept_, cptr_out)\n", " return out\n", "\n", "predict_clr(x, clr)"]}, {"cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 7.52 \u00b5s Ecart-type 2.34 \u00b5s (with 100 runs) in [6.20 \u00b5s, 10.42 \u00b5s]\n"]}], "source": ["memo_time.append(timeexe(\"cffi-linreg-wrapped\", \"predict_clr(x, clr)\"))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Cela reste plus rapide."]}, {"cell_type": "markdown", "metadata": {}, "source": ["### cffi - seconde version\n", "\n", "Comme on construit la fonction en dynamique (le code est connu lors de l'ex\u00e9cution), on peut facilement se passer de la boucle et \u00e9crire le code sans boucle et avec les coefficients."]}, {"cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [{"data": {"text/plain": ["'-35.81159277952622*x[0] + -267.39308260812277*x[1] + 503.56121841083586*x[2] + 337.87944183803455*x[3] + -577.2725523621144*x[4] + 373.6293947654621*x[5] + -99.69779326605845*x[6] + 78.39842093764699*x[7] + 656.5430915289373*x[8] + 80.33839980437061*x[9]'"]}, "execution_count": 22, "metadata": {}, "output_type": "execute_result"}], "source": ["res = \" + \".join(\"{0}*x[{1}]\".format(c, i) for i, c in enumerate(clr.coef_))\n", "res"]}, {"cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["\n", " static int linreg_custom(double * x, double * out)\n", " {\n", " out[0] = 152.69613239933642 + -35.81159277952622*x[0] + -267.39308260812277*x[1] + 503.56121841083586*x[2] + 337.87944183803455*x[3] + -577.2725523621144*x[4] + 373.6293947654621*x[5] + -99.69779326605845*x[6] + 78.39842093764699*x[7] + 656.5430915289373*x[8] + 80.33839980437061*x[9];\n", " }\n", "\n"]}], "source": ["code = \"\"\"\n", " static int linreg_custom(double * x, double * out)\n", " {{\n", " out[0] = {0} + {1};\n", " }}\n", "\"\"\".format(clr.intercept_, res)\n", "print(code)"]}, {"cell_type": "code", "execution_count": 23, "metadata": {"scrolled": false}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["generating .\\_linear_regression_custom.c\n", "the current directory is 'C:\\\\xavierdupre\\\\__home_\\\\GitHub\\\\ensae_teaching_cs\\\\_doc\\\\notebooks\\\\2a'\n", "running build_ext\n", "building '_linear_regression_custom' extension\n", "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\bin\\HostX86\\x64\\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -IC:\\Python395_x64\\include -IC:\\Python395_x64\\include -IC:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\ATLMFC\\include -IC:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\include -IC:\\Program Files (x86)\\Windows Kits\\NETFXSDK\\4.8\\include\\um -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\ucrt -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\shared -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\um -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\winrt -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\cppwinrt /Tc_linear_regression_custom.c /Fo.\\Release\\_linear_regression_custom.obj\n", "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\bin\\HostX86\\x64\\link.exe /nologo /INCREMENTAL:NO /LTCG /DLL /MANIFEST:EMBED,ID=2 /MANIFESTUAC:NO /LIBPATH:C:\\Python395_x64\\libs /LIBPATH:C:\\Python395_x64\\PCbuild\\amd64 /LIBPATH:C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\ATLMFC\\lib\\x64 /LIBPATH:C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\lib\\x64 /LIBPATH:C:\\Program Files (x86)\\Windows Kits\\NETFXSDK\\4.8\\lib\\um\\x64 /LIBPATH:C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.19041.0\\ucrt\\x64 /LIBPATH:C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.19041.0\\um\\x64 /EXPORT:PyInit__linear_regression_custom .\\Release\\_linear_regression_custom.obj /OUT:.\\_linear_regression_custom.cp39-win_amd64.pyd /IMPLIB:.\\Release\\_linear_regression_custom.cp39-win_amd64.lib\n"]}, {"data": {"text/plain": ["'C:\\\\xavierdupre\\\\__home_\\\\GitHub\\\\ensae_teaching_cs\\\\_doc\\\\notebooks\\\\2a\\\\_linear_regression_custom.cp39-win_amd64.pyd'"]}, "execution_count": 24, "metadata": {}, "output_type": "execute_result"}], "source": ["from cffi import FFI\n", "ffibuilder = FFI()\n", "\n", "ffibuilder.cdef(\"int linreg_custom(double *, double *);\")\n", "ffibuilder.set_source(\"_linear_regression_custom\", code)\n", "ffibuilder.compile(verbose=True)"]}, {"cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([214.72477745])"]}, "execution_count": 25, "metadata": {}, "output_type": "execute_result"}], "source": ["from _linear_regression_custom.lib import linreg_custom\n", "linreg_custom(cptr_x, cptr_out)\n", "out"]}, {"cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 466.52 ns Ecart-type 851.96 ns (with 100 runs) in [315.00 ns, 715.00 ns]\n"]}], "source": ["memo_time.append(timeexe(\"cffi-linreg-custom\", \"linreg_custom(cptr_x, cptr_out)\"))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On a gagn\u00e9 un facteur 2."]}, {"cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([214.72477745])"]}, "execution_count": 27, "metadata": {}, "output_type": "execute_result"}], "source": ["def predict_clr_custom(x):\n", " out = numpy.zeros(1)\n", " ptr_x = x.__array_interface__['data'][0]\n", " cptr_x = ffi.cast(\"double*\", ptr_x)\n", " ptr_out = out.__array_interface__['data'][0]\n", " cptr_out = ffi.cast(\"double*\", ptr_out)\n", " linreg_custom(cptr_x, cptr_out)\n", " return out\n", "\n", "predict_clr_custom(x)"]}, {"cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 5.27 \u00b5s Ecart-type 1.82 \u00b5s (with 100 runs) in [4.42 \u00b5s, 7.77 \u00b5s]\n"]}], "source": ["memo_time.append(timeexe(\"cffi-linreg-custom wrapped\", \"predict_clr_custom(x)\"))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["C'est un peu plus rapide."]}, {"cell_type": "markdown", "metadata": {"collapsed": true}, "source": ["### et en float?\n", "\n", "L'ordinateur fait la distinction entre les [double](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) code sur 64 bit et les [float](https://en.wikipedia.org/wiki/Single-precision_floating-point_format) cod\u00e9 sur 32 bits. La pr\u00e9cision est meilleure dans le premier cas et les calculs sont plus rapides dans le second. Dans le cas du machine learning, on pr\u00e9f\u00e8re la rapidit\u00e9 \u00e0 une perte pr\u00e9cision en pr\u00e9cision qui est souvent compens\u00e9e par l'optimisation inh\u00e9rente \u00e0 tout probl\u00e8me de machine learning. Ce qu'on perd sur une observation, on le retrouve sur une autre."]}, {"cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [{"data": {"text/plain": ["'-35.81159277952622f*x[0] + -267.39308260812277f*x[1] + 503.56121841083586f*x[2] + 337.87944183803455f*x[3] + -577.2725523621144f*x[4] + 373.6293947654621f*x[5] + -99.69779326605845f*x[6] + 78.39842093764699f*x[7] + 656.5430915289373f*x[8] + 80.33839980437061f*x[9]'"]}, "execution_count": 29, "metadata": {}, "output_type": "execute_result"}], "source": ["res = \" + \".join(\"{0}f*x[{1}]\".format(c, i) for i, c in enumerate(clr.coef_))\n", "res"]}, {"cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["\n", " static int linreg_custom_float(float * x, float * out)\n", " {\n", " out[0] = 152.69613239933642f + -35.81159277952622f*x[0] + -267.39308260812277f*x[1] + 503.56121841083586f*x[2] + 337.87944183803455f*x[3] + -577.2725523621144f*x[4] + 373.6293947654621f*x[5] + -99.69779326605845f*x[6] + 78.39842093764699f*x[7] + 656.5430915289373f*x[8] + 80.33839980437061f*x[9];\n", " }\n", "\n"]}], "source": ["code = \"\"\"\n", " static int linreg_custom_float(float * x, float * out)\n", " {{\n", " out[0] = {0}f + {1};\n", " }}\n", "\"\"\".format(clr.intercept_, res)\n", "print(code)"]}, {"cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["generating .\\_linear_regression_custom_float.c\n", "the current directory is 'C:\\\\xavierdupre\\\\__home_\\\\GitHub\\\\ensae_teaching_cs\\\\_doc\\\\notebooks\\\\2a'\n", "running build_ext\n", "building '_linear_regression_custom_float' extension\n", "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\bin\\HostX86\\x64\\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -IC:\\Python395_x64\\include -IC:\\Python395_x64\\include -IC:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\ATLMFC\\include -IC:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\include -IC:\\Program Files (x86)\\Windows Kits\\NETFXSDK\\4.8\\include\\um -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\ucrt -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\shared -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\um -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\winrt -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\cppwinrt /Tc_linear_regression_custom_float.c /Fo.\\Release\\_linear_regression_custom_float.obj\n", "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\bin\\HostX86\\x64\\link.exe /nologo /INCREMENTAL:NO /LTCG /DLL /MANIFEST:EMBED,ID=2 /MANIFESTUAC:NO /LIBPATH:C:\\Python395_x64\\libs /LIBPATH:C:\\Python395_x64\\PCbuild\\amd64 /LIBPATH:C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\ATLMFC\\lib\\x64 /LIBPATH:C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\lib\\x64 /LIBPATH:C:\\Program Files (x86)\\Windows Kits\\NETFXSDK\\4.8\\lib\\um\\x64 /LIBPATH:C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.19041.0\\ucrt\\x64 /LIBPATH:C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.19041.0\\um\\x64 /EXPORT:PyInit__linear_regression_custom_float .\\Release\\_linear_regression_custom_float.obj /OUT:.\\_linear_regression_custom_float.cp39-win_amd64.pyd /IMPLIB:.\\Release\\_linear_regression_custom_float.cp39-win_amd64.lib\n"]}, {"data": {"text/plain": ["'C:\\\\xavierdupre\\\\__home_\\\\GitHub\\\\ensae_teaching_cs\\\\_doc\\\\notebooks\\\\2a\\\\_linear_regression_custom_float.cp39-win_amd64.pyd'"]}, "execution_count": 31, "metadata": {}, "output_type": "execute_result"}], "source": ["from cffi import FFI\n", "ffibuilder = FFI()\n", "\n", "ffibuilder.cdef(\"int linreg_custom_float(float *, float *);\")\n", "ffibuilder.set_source(\"_linear_regression_custom_float\", code)\n", "ffibuilder.compile(verbose=True)"]}, {"cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [], "source": ["from _linear_regression_custom_float.lib import linreg_custom_float"]}, {"cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [], "source": ["def predict_clr_custom_float(x):\n", " out = numpy.zeros(1, dtype=numpy.float32)\n", " ptr_x = x.__array_interface__['data'][0]\n", " cptr_x = ffi.cast ( \"float*\" , ptr_x ) \n", " ptr_out = out.__array_interface__['data'][0]\n", " cptr_out = ffi.cast ( \"float*\" , ptr_out ) \n", " linreg_custom_float(cptr_x, cptr_out)\n", " return out"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Avant d'appeler la fonction, on doit transformer le vecteur iniatial en [float32](https://docs.scipy.org/doc/numpy/user/basics.types.html)."]}, {"cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([1.27301276e+31])"]}, "execution_count": 34, "metadata": {}, "output_type": "execute_result"}], "source": ["x32 = x.astype(numpy.float32)\n", "predict_clr_custom(x32)"]}, {"cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 5.12 \u00b5s Ecart-type 1.60 \u00b5s (with 100 runs) in [4.48 \u00b5s, 6.44 \u00b5s]\n"]}], "source": ["memo_time.append(timeexe(\"cffi-linreg-custom-float wrapped\", \"predict_clr_custom(x32)\"))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["La diff\u00e9rence n'est pas flagrante. Mesurons le code C uniquement m\u00eame si la partie Python ne peut pas \u00eatre compl\u00e8tement \u00e9vit\u00e9e."]}, {"cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 389.19 ns Ecart-type 226.75 ns (with 100 runs) in [317.00 ns, 577.00 ns]\n"]}], "source": ["out = numpy.zeros(1, dtype=numpy.float32)\n", "ptr_x = x32.__array_interface__['data'][0]\n", "cptr_x = ffi.cast ( \"float*\" , ptr_x ) \n", "ptr_out = out.__array_interface__['data'][0]\n", "cptr_out = ffi.cast ( \"float*\" , ptr_out ) \n", "\n", "memo_time.append(timeexe(\"cffi-linreg-custom-float32\", \"linreg_custom_float(cptr_x, cptr_out)\"))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["La diff\u00e9rence n'est pas significative."]}, {"cell_type": "markdown", "metadata": {}, "source": ["### SIMD\n", "\n", "C'est un ensemble d'instructions processeur pour faire des op\u00e9rations terme \u00e0 terme sur 4 float32 aussi rapidement qu'une seule. Le processeur ne peut faire des op\u00e9rations que les nombres sont copi\u00e9s dans ses registres. Le programme passe alors son temps \u00e0 copier des nombres depuis la m\u00e9moire vers les registres du processeur puis \u00e0 faire la copie dans le chemin inverse pour le r\u00e9sultat. Les instructions [SIMD](https://en.wikipedia.org/wiki/SIMD) font gagner du temps du niveau du calcul. Au lieu de faire 4 op\u00e9rations de multiplication terme \u00e0 terme, il n'en fait plus qu'une. Il suffit de savoir comment utiliser ces instructions. Avec Visual Studio, elles sont accessible via ces fonctions [Memory and Initialization Using Streaming SIMD Extensions](https://msdn.microsoft.com/en-us/library/0hey67c0%28v=vs.100%29.aspx).\n", "Le code suivant n'est probablement pas optimal mais il n'est pas trop compliqu\u00e9 \u00e0 suivre."]}, {"cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [], "source": ["code = \"\"\"\n", "#include \n", "\n", "static int linreg_custom_float_simd(float * x, float * out)\n", "{\n", " __m128 c1 = _mm_set_ps(0.3034995490664121f, -237.63931533353392f, 510.5306054362245f, 327.7369804093466f);\n", " __m128 c2 = _mm_set_ps(-814.1317093725389f, 492.81458798373245f, 102.84845219168025f, 184.60648905984064f);\n", " __m128 r1 = _mm_set_ss(152.76430691633442f);\n", " r1 = _mm_add_ss(r1, _mm_mul_ps(c1, _mm_load_ps(x)));\n", " r1 = _mm_add_ss(r1, _mm_mul_ps(c2, _mm_load_ps(x+4)));\n", " float r[4];\n", " _mm_store_ps(r, r1); \n", " out[0] = r[0] + r[1] + r[2] + r[3] + 743.5196167505419f * x[8] + 76.095172216624f * x[9];\n", " return 1;\n", "}\n", "\"\"\""]}, {"cell_type": "code", "execution_count": 37, "metadata": {"scrolled": false}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["generating .\\_linear_regression_custom_float_simd.c\n", "(already up-to-date)\n", "the current directory is 'C:\\\\xavierdupre\\\\__home_\\\\GitHub\\\\ensae_teaching_cs\\\\_doc\\\\notebooks\\\\2a'\n", "running build_ext\n", "building '_linear_regression_custom_float_simd' extension\n", "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\bin\\HostX86\\x64\\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -IC:\\Python395_x64\\include -IC:\\Python395_x64\\include -IC:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\ATLMFC\\include -IC:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\include -IC:\\Program Files (x86)\\Windows Kits\\NETFXSDK\\4.8\\include\\um -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\ucrt -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\shared -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\um -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\winrt -IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\cppwinrt /Tc_linear_regression_custom_float_simd.c /Fo.\\Release\\_linear_regression_custom_float_simd.obj\n", "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\bin\\HostX86\\x64\\link.exe /nologo /INCREMENTAL:NO /LTCG /DLL /MANIFEST:EMBED,ID=2 /MANIFESTUAC:NO /LIBPATH:C:\\Python395_x64\\libs /LIBPATH:C:\\Python395_x64\\PCbuild\\amd64 /LIBPATH:C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\ATLMFC\\lib\\x64 /LIBPATH:C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\lib\\x64 /LIBPATH:C:\\Program Files (x86)\\Windows Kits\\NETFXSDK\\4.8\\lib\\um\\x64 /LIBPATH:C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.19041.0\\ucrt\\x64 /LIBPATH:C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.19041.0\\um\\x64 /EXPORT:PyInit__linear_regression_custom_float_simd .\\Release\\_linear_regression_custom_float_simd.obj /OUT:.\\_linear_regression_custom_float_simd.cp39-win_amd64.pyd /IMPLIB:.\\Release\\_linear_regression_custom_float_simd.cp39-win_amd64.lib\n"]}, {"data": {"text/plain": ["'C:\\\\xavierdupre\\\\__home_\\\\GitHub\\\\ensae_teaching_cs\\\\_doc\\\\notebooks\\\\2a\\\\_linear_regression_custom_float_simd.cp39-win_amd64.pyd'"]}, "execution_count": 38, "metadata": {}, "output_type": "execute_result"}], "source": ["from cffi import FFI\n", "ffibuilder = FFI()\n", "\n", "ffibuilder.cdef(\"int linreg_custom_float_simd(float *, float *);\")\n", "ffibuilder.set_source(\"_linear_regression_custom_float_simd\", code)\n", "ffibuilder.compile(verbose=True)"]}, {"cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [], "source": ["from _linear_regression_custom_float_simd.lib import linreg_custom_float_simd"]}, {"cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([172.00832], dtype=float32)"]}, "execution_count": 40, "metadata": {}, "output_type": "execute_result"}], "source": ["out = numpy.zeros(1, dtype=numpy.float32)\n", "ptr_x = x32.__array_interface__['data'][0]\n", "cptr_x = ffi.cast ( \"float*\" , ptr_x ) \n", "ptr_out = out.__array_interface__['data'][0]\n", "cptr_out = ffi.cast ( \"float*\" , ptr_out ) \n", "\n", "linreg_custom_float_simd(cptr_x, cptr_out)\n", "out"]}, {"cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 418.99 ns Ecart-type 387.18 ns (with 100 runs) in [299.00 ns, 631.00 ns]\n"]}], "source": ["memo_time.append(timeexe(\"cffi-linreg-custom-float32-simd\", \"linreg_custom_float_simd(cptr_x, cptr_out)\"))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["C'est l\u00e9g\u00e8rement mieux, quelques r\u00e9f\u00e9rences :\n", "\n", "* [aligned_vs_unaligned_load.c](https://gist.github.com/rmcgibbo/7689820) : c'est du code mais facile \u00e0 lire.\n", "* [How to Write Fast Numerical Code](https://www.inf.ethz.ch/personal/markusp/teaching/263-2300-ETH-spring11/slides/class17.pdf)\n", "\n", "Les processeurs \u00e9voluent au fil du temps, 4 float, 8 float, [SIMD2](https://msdn.microsoft.com/en-us/library/kcwz153a%28v=vs.100%29.aspx), [FMA4 Intrinsics Added for Visual Studio 2010 SP1](https://msdn.microsoft.com/en-us/library/gg445134%28v=vs.100%29.aspx), [AVX](https://software.intel.com/en-us/articles/introduction-to-intel-advanced-vector-extensions/)."]}, {"cell_type": "markdown", "metadata": {"collapsed": true}, "source": ["### R\u00e9\u00e9criture purement Python\n", "\n", "On continue avec uniquement du Python sans numpy."]}, {"cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [{"data": {"text/plain": ["[-35.81159277952622,\n", " -267.39308260812277,\n", " 503.56121841083586,\n", " 337.87944183803455,\n", " -577.2725523621144,\n", " 373.6293947654621,\n", " -99.69779326605845,\n", " 78.39842093764699,\n", " 656.5430915289373,\n", " 80.33839980437061]"]}, "execution_count": 42, "metadata": {}, "output_type": "execute_result"}], "source": ["coef = clr.coef_\n", "list(coef)"]}, {"cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [{"data": {"text/plain": ["'152.69613239933642+x[0]*(-35.81159277952622)+x[1]*(-267.39308260812277)+x[2]*(503.56121841083586)+x[3]*(337.87944183803455)+x[4]*(-577.2725523621144)+x[5]*(373.6293947654621)+x[6]*(-99.69779326605845)+x[7]*(78.39842093764699)+x[8]*(656.5430915289373)+x[9]*(80.33839980437061)'"]}, "execution_count": 43, "metadata": {}, "output_type": "execute_result"}], "source": ["code = str(clr.intercept_) + \"+\" + \"+\".join(\"x[{0}]*({1})\".format(i, c) for i, c in enumerate(coef))\n", "code"]}, {"cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [{"data": {"text/plain": ["211.03463170273153"]}, "execution_count": 44, "metadata": {}, "output_type": "execute_result"}], "source": ["def predict_clr_python(x):\n", " return 152.764306916+x[0]*0.3034995490664121+x[1]*(-237.63931533353392)+x[2]*510.5306054362245+ \\\n", " x[3]*327.7369804093466+ \\\n", " x[4]*(-814.1317093725389)+x[5]*492.81458798373245+x[6]*102.84845219168025+ \\\n", " x[7]*184.60648905984064+x[8]*743.5196167505419+x[9]*76.095172216624\n", " \n", "predict_clr_python(x[0])"]}, {"cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 2.02 \u00b5s Ecart-type 670.45 ns (with 100 runs) in [1.70 \u00b5s, 2.73 \u00b5s]\n"]}], "source": ["z = list(x[0])\n", "memo_time.append(timeexe(\"python-linreg-custom\", \"predict_clr_python(z)\"))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["De fa\u00e7on assez surprenante, c'est plut\u00f4t rapide. Et si on y mettait une boucle."]}, {"cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [{"data": {"text/plain": ["214.72477744760596"]}, "execution_count": 46, "metadata": {}, "output_type": "execute_result"}], "source": ["def predict_clr_python_loop(x, coef, intercept): \n", " return intercept + sum(a*b for a, b in zip(x, coef))\n", " \n", "predict_clr_python_loop(x[0], list(clr.coef_), clr.intercept_)"]}, {"cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 3.54 \u00b5s Ecart-type 1.31 \u00b5s (with 100 runs) in [2.68 \u00b5s, 6.16 \u00b5s]\n"]}], "source": ["coef = list(clr.coef_)\n", "intercept = clr.intercept_\n", "memo_time.append(timeexe(\"python-linreg\", \"predict_clr_python_loop(z, coef, intercept)\"))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["A peine plus long."]}, {"cell_type": "markdown", "metadata": {}, "source": ["### R\u00e9\u00e9criture avec Python et numpy"]}, {"cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [{"data": {"text/plain": ["214.72477744760596"]}, "execution_count": 48, "metadata": {}, "output_type": "execute_result"}], "source": ["def predict_clr_numpy(x, coef, intercept): \n", " return intercept + numpy.dot(coef, x).sum()\n", " \n", "predict_clr_numpy(x[0], clr.coef_, clr.intercept_)"]}, {"cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 8.08 \u00b5s Ecart-type 3.44 \u00b5s (with 100 runs) in [6.44 \u00b5s, 12.16 \u00b5s]\n"]}], "source": ["memo_time.append(timeexe(\"numpy-linreg-numpy\", \"predict_clr_numpy(z, coef, clr.intercept_)\"))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Les dimensions des tableaux sont trop petites pour que le calcul matriciel apporte une diff\u00e9rence. On se retrouve dans le cas *cffi* o\u00f9 les \u00e9changes Python - C grignotent tout le temps de calcul."]}, {"cell_type": "markdown", "metadata": {}, "source": ["### numba\n", "\n", "[numba](http://numba.pydata.org/) essaye de compiler \u00e0 la vol\u00e9e des bouts de codes \u00e9crits en Python. On induque quelle fonction optimiser en faisant pr\u00e9c\u00e9der la fonction de ``@jit``. Toutes les \u00e9critures ne fonctionnent, typiquement, certaines listes en compr\u00e9hension soul\u00e8vent une exception. Il faut donc \u00e9crire son code en Python d'une fa\u00e7on assez proche de ce qu'il serait en C."]}, {"cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [], "source": ["from numba import jit"]}, {"cell_type": "code", "execution_count": 50, "metadata": {"scrolled": false}, "outputs": [{"name": "stderr", "output_type": "stream", "text": ["C:\\Python395_x64\\lib\\site-packages\\numba\\core\\ir_utils.py:2152: NumbaPendingDeprecationWarning: \u001b[1m\n", "Encountered the use of a type that is scheduled for deprecation: type 'reflected list' found for argument 'x' of function 'predict_clr_numba'.\n", "\n", "For more information visit https://numba.readthedocs.io/en/stable/reference/deprecation.html#deprecation-of-reflection-for-list-and-set-types\n", "\u001b[1m\n", "File \"\", line 2:\u001b[0m\n", "\u001b[1m@jit\n", "\u001b[1mdef predict_clr_numba(x, coef, intercept):\n", "\u001b[0m\u001b[1m^\u001b[0m\u001b[0m\n", "\u001b[0m\n", " warnings.warn(NumbaPendingDeprecationWarning(msg, loc=loc))\n"]}, {"data": {"text/plain": ["214.724777447606"]}, "execution_count": 51, "metadata": {}, "output_type": "execute_result"}], "source": ["@jit\n", "def predict_clr_numba(x, coef, intercept):\n", " s = intercept\n", " for i in range(0, len(x)):\n", " s += coef[i] * x[i]\n", " return s\n", " \n", "predict_clr_numba(z, clr.coef_, clr.intercept_)"]}, {"cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 23.77 \u00b5s Ecart-type 7.36 \u00b5s (with 100 runs) in [19.99 \u00b5s, 37.64 \u00b5s]\n"]}], "source": ["memo_time.append(timeexe(\"numba-linreg-notype\", \"predict_clr_numba(z, clr.coef_, clr.intercept_)\"))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Plut\u00f4t rapide !"]}, {"cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [{"data": {"text/plain": ["214.724777447606"]}, "execution_count": 53, "metadata": {}, "output_type": "execute_result"}], "source": ["@jit('double(double[:], double[:], double)')\n", "def predict_clr_numba_cast(x, coef, intercept):\n", " s = intercept\n", " for i in range(0, len(x)):\n", " s += coef[i] * x[i]\n", " return s\n", " \n", "# La fonction ne fonctionne qu'avec un numpy.array car le langage C est fortement typ\u00e9.\n", "predict_clr_numba_cast(x[0], clr.coef_, clr.intercept_)"]}, {"cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 948.20 ns Ecart-type 411.47 ns (with 100 runs) in [759.00 ns, 1.68 \u00b5s]\n"]}], "source": ["memo_time.append(timeexe(\"numba-linreg-type\", \"predict_clr_numba_cast(x[0], clr.coef_, clr.intercept_)\"))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On voit que plus on donne d'information au compilateur, plus il est capable d'optimiser."]}, {"cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [{"data": {"text/plain": ["214.7247772216797"]}, "execution_count": 55, "metadata": {}, "output_type": "execute_result"}], "source": ["@jit('float32(float32[:], float32[:], float32)')\n", "def predict_clr_numba_cast_float(x, coef, intercept):\n", " s = intercept\n", " for i in range(0, len(x)):\n", " s += coef[i] * x[i]\n", " return s\n", " \n", "# La fonction ne fonctionne qu'avec un numpy.array car le langage C est fortement typ\u00e9.\n", "x32 = x[0].astype(numpy.float32)\n", "c32 = clr.coef_.astype(numpy.float32)\n", "i32 = numpy.float32(clr.intercept_)\n", "predict_clr_numba_cast_float(x32, c32, i32)"]}, {"cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 707.08 ns Ecart-type 268.64 ns (with 100 runs) in [565.00 ns, 1.25 \u00b5s]\n"]}], "source": ["memo_time.append(timeexe(\"numba-linreg-type-float32\", \"predict_clr_numba_cast_float(x32, c32, i32)\"))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On essaye avec les coefficients dans la fonction."]}, {"cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [{"data": {"text/plain": ["211.034631692416"]}, "execution_count": 57, "metadata": {}, "output_type": "execute_result"}], "source": ["@jit('double(double[:])')\n", "def predict_clr_numba_cast_custom(x):\n", " coef = [ 3.03499549e-01, -2.37639315e+02, 5.10530605e+02, 3.27736980e+02,\n", " -8.14131709e+02, 4.92814588e+02, 1.02848452e+02, 1.84606489e+02,\n", " 7.43519617e+02, 7.60951722e+01]\n", " s = 152.76430691633442\n", " for i in range(0, len(x)):\n", " s += coef[i] * x[i]\n", " return s\n", " \n", "predict_clr_numba_cast_custom(x[0])"]}, {"cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 824.35 ns Ecart-type 371.36 ns (with 100 runs) in [652.00 ns, 1.56 \u00b5s]\n"]}], "source": ["memo_time.append(timeexe(\"numba-linreg-type-custom\", \"predict_clr_numba_cast_custom(x[0])\"))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On se rapproche des temps obtenus avec *cffi* sans *wrapping*, cela signifie que *numba* fait un bien meilleur travail \u00e0 ce niveau que le wrapper rapidement cr\u00e9\u00e9."]}, {"cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [{"name": "stderr", "output_type": "stream", "text": [":1: NumbaWarning: \u001b[1m\n", "Compilation is falling back to object mode WITH looplifting enabled because Function \"predict_clr_numba_numpy\" failed type inference due to: \u001b[1m\u001b[1mUnknown attribute 'sum' of type float64\n", "\u001b[1m\n", "File \"\", line 3:\u001b[0m\n", "\u001b[1mdef predict_clr_numba_numpy(x, coef, intercept): \n", "\u001b[1m return intercept + numpy.dot(coef, x).sum()\n", "\u001b[0m \u001b[1m^\u001b[0m\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\u001b[1mDuring: typing of get attribute at (3)\u001b[0m\n", "\u001b[1m\n", "File \"\", line 3:\u001b[0m\n", "\u001b[1mdef predict_clr_numba_numpy(x, coef, intercept): \n", "\u001b[1m return intercept + numpy.dot(coef, x).sum()\n", "\u001b[0m \u001b[1m^\u001b[0m\u001b[0m\n", "\u001b[0m\n", " @jit('double(double[:], double[:], double)')\n", "C:\\Python395_x64\\lib\\site-packages\\numba\\core\\object_mode_passes.py:151: NumbaWarning: \u001b[1mFunction \"predict_clr_numba_numpy\" was compiled in object mode without forceobj=True.\n", "\u001b[1m\n", "File \"\", line 2:\u001b[0m\n", "\u001b[1m@jit('double(double[:], double[:], double)')\n", "\u001b[1mdef predict_clr_numba_numpy(x, coef, intercept): \n", "\u001b[0m\u001b[1m^\u001b[0m\u001b[0m\n", "\u001b[0m\n", " warnings.warn(errors.NumbaWarning(warn_msg,\n", "C:\\Python395_x64\\lib\\site-packages\\numba\\core\\object_mode_passes.py:161: NumbaDeprecationWarning: \u001b[1m\n", "Fall-back from the nopython compilation path to the object mode compilation path has been detected, this is deprecated behaviour.\n", "\n", "For more information visit https://numba.readthedocs.io/en/stable/reference/deprecation.html#deprecation-of-object-mode-fall-back-behaviour-when-using-jit\n", "\u001b[1m\n", "File \"\", line 2:\u001b[0m\n", "\u001b[1m@jit('double(double[:], double[:], double)')\n", "\u001b[1mdef predict_clr_numba_numpy(x, coef, intercept): \n", "\u001b[0m\u001b[1m^\u001b[0m\u001b[0m\n", "\u001b[0m\n", " warnings.warn(errors.NumbaDeprecationWarning(msg,\n"]}, {"data": {"text/plain": ["214.72477744760596"]}, "execution_count": 59, "metadata": {}, "output_type": "execute_result"}], "source": ["@jit('double(double[:], double[:], double)')\n", "def predict_clr_numba_numpy(x, coef, intercept): \n", " return intercept + numpy.dot(coef, x).sum()\n", "\n", "predict_clr_numba_numpy(x[0], clr.coef_, clr.intercept_)"]}, {"cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 5.15 \u00b5s Ecart-type 1.78 \u00b5s (with 100 runs) in [4.37 \u00b5s, 6.00 \u00b5s]\n"]}], "source": ["memo_time.append(timeexe(\"numba-linreg-type-numpy\", \"predict_clr_numba_numpy(x[0], clr.coef_, clr.intercept_)\"))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["*numba* est moins performant quand *numpy* est impliqu\u00e9 car le code de numpy n'est pas r\u00e9\u00e9crit, il est appel\u00e9."]}, {"cell_type": "markdown", "metadata": {}, "source": ["### cython\n", "\n", "[cython](http://cython.org/) permet de cr\u00e9er des extensions C de plus grande envergure que *numba*. C'est l'option choisie par [scikit-learn](http://scikit-learn.org/stable/). Il vaut mieux conna\u00eetre le C pour s'en servir et l\u00e0 encore, l'objectif est de r\u00e9duire les \u00e9changes Python / C qui co\u00fbtent cher."]}, {"cell_type": "code", "execution_count": 60, "metadata": {}, "outputs": [], "source": ["%load_ext cython"]}, {"cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [], "source": ["%%cython\n", "def predict_clr_cython(x, coef, intercept):\n", " s = intercept\n", " for i in range(0, len(x)):\n", " s += coef[i] * x[i]\n", " return s"]}, {"cell_type": "code", "execution_count": 62, "metadata": {}, "outputs": [{"data": {"text/plain": ["214.724777447606"]}, "execution_count": 63, "metadata": {}, "output_type": "execute_result"}], "source": ["predict_clr_cython(x[0], clr.coef_, clr.intercept_)"]}, {"cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 2.71 \u00b5s Ecart-type 1.60 \u00b5s (with 100 runs) in [1.92 \u00b5s, 7.19 \u00b5s]\n"]}], "source": ["memo_time.append(timeexe(\"cython-linreg\", \"predict_clr_cython(x[0], clr.coef_, clr.intercept_)\"))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Cython fait moins bien que *numba* dans notre cas et l'optimisation propos\u00e9e est assez proche du temps d\u00e9j\u00e0 obtenue avec le langage Python seul. Cela est d\u00fb au fait que la plupart des objets tels que du code associ\u00e9 aux listes ou aux dictionnaires ont \u00e9t\u00e9 r\u00e9\u00e9crits en C."]}, {"cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [], "source": ["%%cython\n", "cimport numpy as npc\n", "\n", "def predict_clr_cython_type(npc.ndarray[double, ndim=1, mode='c'] x,\n", " npc.ndarray[double, ndim=1, mode='c'] coef,\n", " double intercept):\n", " cdef double s = intercept\n", " for i in range(0, x.shape[0]):\n", " s += coef[i] * x[i]\n", " return s"]}, {"cell_type": "code", "execution_count": 65, "metadata": {}, "outputs": [{"data": {"text/plain": ["214.724777447606"]}, "execution_count": 66, "metadata": {}, "output_type": "execute_result"}], "source": ["predict_clr_cython_type(x[0], clr.coef_, clr.intercept_)"]}, {"cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 721.31 ns Ecart-type 399.10 ns (with 100 runs) in [533.00 ns, 1.44 \u00b5s]\n"]}], "source": ["memo_time.append(timeexe(\n", " \"cython-linreg-type\", \"predict_clr_cython_type(x[0], clr.coef_, clr.intercept_)\"))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Le temps est quasi identique avec un \u00e9cart type moins grand de fa\u00e7on significative."]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Une derni\u00e8re option : ONNX\n", "\n", "[ONNX](https://onnx.ai/) est un format de s\u00e9rialisation qui permet de d\u00e9crire un mod\u00e8le de mod\u00e8le de machine learning ou de deep learning. Cela permet de dissocer le mod\u00e8le de la librairie qui a servi \u00e0 le produire (voir [ML.net and ONNX](http://www.xavierdupre.fr/app/machinelearningext/helpsphinx/aonnx.html))."]}, {"cell_type": "code", "execution_count": 67, "metadata": {}, "outputs": [{"name": "stderr", "output_type": "stream", "text": ["Error in sys.excepthook:\n", "Traceback (most recent call last):\n", " File \"C:\\Python395_x64\\lib\\site-packages\\IPython\\core\\interactiveshell.py\", line 1934, in showtraceback\n", " stb = value._render_traceback_()\n", "AttributeError: 'RuntimeError' object has no attribute '_render_traceback_'\n", "\n", "During handling of the above exception, another exception occurred:\n", "\n", "Traceback (most recent call last):\n", " File \"C:\\Python395_x64\\lib\\site-packages\\IPython\\core\\interactiveshell.py\", line 1936, in showtraceback\n", " stb = self.InteractiveTB.structured_traceback(etype,\n", " File \"C:\\Python395_x64\\lib\\site-packages\\IPython\\core\\ultratb.py\", line 1105, in structured_traceback\n", " return FormattedTB.structured_traceback(\n", " File \"C:\\Python395_x64\\lib\\site-packages\\IPython\\core\\ultratb.py\", line 999, in structured_traceback\n", " return VerboseTB.structured_traceback(\n", " File \"C:\\Python395_x64\\lib\\site-packages\\IPython\\core\\ultratb.py\", line 851, in structured_traceback\n", " assert etb is not None\n", "AssertionError\n", "\n", "Original exception was:\n", "RuntimeError: module compiled against API version 0xf but this version of numpy is 0xe\n"]}, {"name": "stdout", "output_type": "stream", "text": ["onnx, skl2onnx, onnxruntime sont disponibles.\n"]}], "source": ["try:\n", " from skl2onnx import convert_sklearn\n", " from skl2onnx.common.data_types import FloatTensorType\n", " import onnxruntime\n", " import onnx\n", " ok_onnx = True\n", " print(\"onnx, skl2onnx, onnxruntime sont disponibles.\")\n", " \n", " def save_model(onnx_model, filename):\n", " with open(filename, \"wb\") as f:\n", " f.write(onnx_model.SerializeToString())\n", "except ImportError as e:\n", " print(\"La suite requiert onnx, skl2onnx et onnxruntime.\")\n", " print(e)\n", " ok_onnx = False"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On convertit le mod\u00e8le au format [ONNX](https://onnx.ai/)."]}, {"cell_type": "code", "execution_count": 68, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Mod\u00e8le s\u00e9rialis\u00e9 au format ONNX\n", "ir_version: 6\n", "producer_name: \"skl2onnx\"\n", "producer_version: \"1.10.4\"\n", "domain: \"ai.onnx\"\n", "model_version: 0\n", "doc_string: \"\"\n", "graph {\n", " node {\n", " input: \"input\"\n", " output: \"variable\"\n", " name: \"LinearRegressor\"\n", " op_type: \"LinearRegressor\"\n", " attribute {\n", " name: \"coefficients\"\n", " floats: -35.81159210205078\n", " floats: -267.3930969238281\n", " floats: 503.56121826171875\n", " floats: 337.87945556640625\n", " floats: -577.2725219726562\n", " floats: 373.62939453125\n", " floats: -99.69779205322266\n", " floats: 78.39842224121094\n", " floats: 656.5430908203125\n", " floats: 80.3384017944336\n", " type: FLOATS\n", " }\n", " attribute {\n", " name: \"intercepts\"\n", " floats: 152.69613647460938\n", " type: FLOATS\n", " }\n", " domain: \"ai.onnx.ml\"\n", " }\n", " name: \"model\"\n", " input {\n", " name: \"input\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " }\n", " dim {\n", " dim_value: 10\n", " }\n", " }\n", " }\n", " }\n", " }\n", " output {\n", " name: \"variable\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " }\n", " dim {\n", " dim_value: 1\n", " }\n", " }\n", " }\n", " }\n", " }\n", "}\n", "opset_import {\n", " domain: \"ai.onnx.ml\"\n", " version: 1\n", "}\n", "opset_import {\n", " domain: \"\"\n", " version: 11\n", "}\n", "\n"]}], "source": ["if ok_onnx:\n", " onnx_model = convert_sklearn(\n", " clr, 'model', [('input', FloatTensorType([None, clr.coef_.shape[0]]))],\n", " target_opset=11)\n", " onnx_model.ir_version = 6\n", " save_model(onnx_model, 'model.onnx')\n", " \n", " model_onnx = onnx.load('model.onnx')\n", " print(\"Mod\u00e8le s\u00e9rialis\u00e9 au format ONNX\")\n", " print(model_onnx)\n", "else:\n", " print(\"onnx, onnxmltools, onnxruntime sont disponibles.\")"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On calcule les pr\u00e9dictions. Le module {onnxruntime](https://docs.microsoft.com/en-us/python/api/overview/azure/onnx/intro?view=azure-onnx-py) optimise les calculs pour des mod\u00e8les de deep learning. Cela explique pourquoi tous les calculs sont r\u00e9alis\u00e9s avec des r\u00e9els repr\u00e9sent\u00e9s sur 4 octets [numpy.float32](https://docs.scipy.org/doc/numpy/user/basics.types.html?highlight=float32)."]}, {"cell_type": "code", "execution_count": 69, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Input: NodeArg(name='input', type='tensor(float)', shape=[None, 10])\n", "Output: NodeArg(name='variable', type='tensor(float)', shape=[None, 1])\n", "Prediction: [array([[214.72478]], dtype=float32)]\n"]}], "source": ["if ok_onnx:\n", " sess = onnxruntime.InferenceSession(\"model.onnx\")\n", " for i in sess.get_inputs():\n", " print('Input:', i)\n", " for o in sess.get_outputs():\n", " print('Output:', o)\n", " \n", " def predict_onnxrt(x): \n", " return sess.run([\"variable\"], {'input': x})\n", " \n", " print(\"Prediction:\", predict_onnxrt(x.astype(numpy.float32)))"]}, {"cell_type": "code", "execution_count": 70, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 13.00 \u00b5s Ecart-type 7.69 \u00b5s (with 100 runs) in [9.71 \u00b5s, 23.64 \u00b5s]\n", "Moyenne: 12.69 \u00b5s Ecart-type 1.93 \u00b5s (with 100 runs) in [11.29 \u00b5s, 16.23 \u00b5s]\n"]}], "source": ["if ok_onnx:\n", " x32 = x.astype(numpy.float32)\n", " memo_time.append(timeexe(\"onnxruntime-float32\", \"predict_onnxrt(x32)\"))\n", " memo_time.append(timeexe(\"onnxruntime-float64\", \"predict_onnxrt(x.astype(numpy.float32))\"))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### R\u00e9capitulatif"]}, {"cell_type": "code", "execution_count": 71, "metadata": {}, "outputs": [{"data": {"text/html": ["
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
averagedeviationfirstfirst3last3repeatmin5max5coderun
legend
cffi-linreg-custom-float323.891910e-072.267541e-075.608000e-062.196000e-063.763333e-0710003.170000e-075.770000e-07linreg_custom_float(cptr_x, cptr_out)100
cffi-linreg-custom-float32-simd4.189890e-073.871792e-071.091200e-053.949667e-062.996667e-0710002.990000e-076.310000e-07linreg_custom_float_simd(cptr_x, cptr_out)100
cffi-linreg-custom4.665150e-078.519581e-072.679900e-059.352667e-063.256667e-0710003.150000e-077.150000e-07linreg_custom(cptr_x, cptr_out)100
numba-linreg-type-float327.070790e-072.686359e-071.162000e-061.083333e-065.663333e-0710005.650000e-071.249000e-06predict_clr_numba_cast_float(x32, c32, i32)100
cython-linreg-type7.213150e-073.991047e-071.252000e-068.300000e-075.513333e-0710005.330000e-071.443000e-06predict_clr_cython_type(x[0], clr.coef_, clr.i...100
numba-linreg-type-custom8.243540e-073.713608e-077.940000e-077.156667e-076.543333e-0710006.520000e-071.558000e-06predict_clr_numba_cast_custom(x[0])100
cffi-linreg8.313670e-077.080831e-076.414000e-063.244000e-064.170000e-0710004.160000e-071.519000e-06lib.linreg(n, cptr_x, cptr_coef, clr.intercept...100
numba-linreg-type9.482040e-074.114651e-079.350000e-078.663333e-077.596667e-0710007.590000e-071.678000e-06predict_clr_numba_cast(x[0], clr.coef_, clr.in...100
python-linreg-custom2.018942e-066.704544e-075.511000e-064.254667e-061.703667e-0610001.696000e-062.731000e-06predict_clr_python(z)100
cython-linreg2.706254e-061.597806e-065.083000e-065.419333e-062.126000e-0610001.920000e-067.194000e-06predict_clr_cython(x[0], clr.coef_, clr.interc...100
python-linreg3.539523e-061.306156e-068.761000e-067.510000e-062.779667e-0610002.681000e-066.164000e-06predict_clr_python_loop(z, coef, intercept)100
cffi-linreg-custom-float wrapped5.123886e-061.598363e-061.200400e-051.176767e-054.483000e-0610004.477000e-066.436000e-06predict_clr_custom(x32)100
numba-linreg-type-numpy5.147404e-061.775723e-061.874100e-051.572433e-054.474333e-0610004.374000e-065.996000e-06predict_clr_numba_numpy(x[0], clr.coef_, clr.i...100
cffi-linreg-custom wrapped5.274568e-061.823247e-062.166200e-052.268700e-055.626667e-0610004.422000e-067.773000e-06predict_clr_custom(x)100
cffi-linreg-wrapped7.519599e-062.343424e-061.580000e-052.028933e-056.263333e-0610006.201000e-061.041900e-05predict_clr(x, clr)100
numpy-linreg-numpy8.081947e-063.442724e-063.679000e-053.075167e-056.525667e-0610006.442000e-061.216200e-05predict_clr_numpy(z, coef, clr.intercept_)100
onnxruntime-float641.269215e-051.926911e-061.742200e-051.337233e-051.133667e-0510001.129500e-051.623200e-05predict_onnxrt(x.astype(numpy.float32))100
onnxruntime-float321.299773e-057.686900e-062.281400e-051.689933e-051.009533e-0510009.713000e-062.363700e-05predict_onnxrt(x32)100
numba-linreg-notype2.376539e-057.362380e-063.079800e-052.445400e-053.723367e-0510001.998900e-053.763900e-05predict_clr_numba(z, clr.coef_, clr.intercept_)100
sklearn.predict4.550096e-056.337585e-067.724200e-056.447133e-054.143867e-0510004.087300e-055.295400e-05clr.predict(z)100
\n", "
"], "text/plain": [" average deviation first \\\n", "legend \n", "cffi-linreg-custom-float32 3.891910e-07 2.267541e-07 5.608000e-06 \n", "cffi-linreg-custom-float32-simd 4.189890e-07 3.871792e-07 1.091200e-05 \n", "cffi-linreg-custom 4.665150e-07 8.519581e-07 2.679900e-05 \n", "numba-linreg-type-float32 7.070790e-07 2.686359e-07 1.162000e-06 \n", "cython-linreg-type 7.213150e-07 3.991047e-07 1.252000e-06 \n", "numba-linreg-type-custom 8.243540e-07 3.713608e-07 7.940000e-07 \n", "cffi-linreg 8.313670e-07 7.080831e-07 6.414000e-06 \n", "numba-linreg-type 9.482040e-07 4.114651e-07 9.350000e-07 \n", "python-linreg-custom 2.018942e-06 6.704544e-07 5.511000e-06 \n", "cython-linreg 2.706254e-06 1.597806e-06 5.083000e-06 \n", "python-linreg 3.539523e-06 1.306156e-06 8.761000e-06 \n", "cffi-linreg-custom-float wrapped 5.123886e-06 1.598363e-06 1.200400e-05 \n", "numba-linreg-type-numpy 5.147404e-06 1.775723e-06 1.874100e-05 \n", "cffi-linreg-custom wrapped 5.274568e-06 1.823247e-06 2.166200e-05 \n", "cffi-linreg-wrapped 7.519599e-06 2.343424e-06 1.580000e-05 \n", "numpy-linreg-numpy 8.081947e-06 3.442724e-06 3.679000e-05 \n", "onnxruntime-float64 1.269215e-05 1.926911e-06 1.742200e-05 \n", "onnxruntime-float32 1.299773e-05 7.686900e-06 2.281400e-05 \n", "numba-linreg-notype 2.376539e-05 7.362380e-06 3.079800e-05 \n", "sklearn.predict 4.550096e-05 6.337585e-06 7.724200e-05 \n", "\n", " first3 last3 repeat \\\n", "legend \n", "cffi-linreg-custom-float32 2.196000e-06 3.763333e-07 1000 \n", "cffi-linreg-custom-float32-simd 3.949667e-06 2.996667e-07 1000 \n", "cffi-linreg-custom 9.352667e-06 3.256667e-07 1000 \n", "numba-linreg-type-float32 1.083333e-06 5.663333e-07 1000 \n", "cython-linreg-type 8.300000e-07 5.513333e-07 1000 \n", "numba-linreg-type-custom 7.156667e-07 6.543333e-07 1000 \n", "cffi-linreg 3.244000e-06 4.170000e-07 1000 \n", "numba-linreg-type 8.663333e-07 7.596667e-07 1000 \n", "python-linreg-custom 4.254667e-06 1.703667e-06 1000 \n", "cython-linreg 5.419333e-06 2.126000e-06 1000 \n", "python-linreg 7.510000e-06 2.779667e-06 1000 \n", "cffi-linreg-custom-float wrapped 1.176767e-05 4.483000e-06 1000 \n", "numba-linreg-type-numpy 1.572433e-05 4.474333e-06 1000 \n", "cffi-linreg-custom wrapped 2.268700e-05 5.626667e-06 1000 \n", "cffi-linreg-wrapped 2.028933e-05 6.263333e-06 1000 \n", "numpy-linreg-numpy 3.075167e-05 6.525667e-06 1000 \n", "onnxruntime-float64 1.337233e-05 1.133667e-05 1000 \n", "onnxruntime-float32 1.689933e-05 1.009533e-05 1000 \n", "numba-linreg-notype 2.445400e-05 3.723367e-05 1000 \n", "sklearn.predict 6.447133e-05 4.143867e-05 1000 \n", "\n", " min5 max5 \\\n", "legend \n", "cffi-linreg-custom-float32 3.170000e-07 5.770000e-07 \n", "cffi-linreg-custom-float32-simd 2.990000e-07 6.310000e-07 \n", "cffi-linreg-custom 3.150000e-07 7.150000e-07 \n", "numba-linreg-type-float32 5.650000e-07 1.249000e-06 \n", "cython-linreg-type 5.330000e-07 1.443000e-06 \n", "numba-linreg-type-custom 6.520000e-07 1.558000e-06 \n", "cffi-linreg 4.160000e-07 1.519000e-06 \n", "numba-linreg-type 7.590000e-07 1.678000e-06 \n", "python-linreg-custom 1.696000e-06 2.731000e-06 \n", "cython-linreg 1.920000e-06 7.194000e-06 \n", "python-linreg 2.681000e-06 6.164000e-06 \n", "cffi-linreg-custom-float wrapped 4.477000e-06 6.436000e-06 \n", "numba-linreg-type-numpy 4.374000e-06 5.996000e-06 \n", "cffi-linreg-custom wrapped 4.422000e-06 7.773000e-06 \n", "cffi-linreg-wrapped 6.201000e-06 1.041900e-05 \n", "numpy-linreg-numpy 6.442000e-06 1.216200e-05 \n", "onnxruntime-float64 1.129500e-05 1.623200e-05 \n", "onnxruntime-float32 9.713000e-06 2.363700e-05 \n", "numba-linreg-notype 1.998900e-05 3.763900e-05 \n", "sklearn.predict 4.087300e-05 5.295400e-05 \n", "\n", " code \\\n", "legend \n", "cffi-linreg-custom-float32 linreg_custom_float(cptr_x, cptr_out) \n", "cffi-linreg-custom-float32-simd linreg_custom_float_simd(cptr_x, cptr_out) \n", "cffi-linreg-custom linreg_custom(cptr_x, cptr_out) \n", "numba-linreg-type-float32 predict_clr_numba_cast_float(x32, c32, i32) \n", "cython-linreg-type predict_clr_cython_type(x[0], clr.coef_, clr.i... \n", "numba-linreg-type-custom predict_clr_numba_cast_custom(x[0]) \n", "cffi-linreg lib.linreg(n, cptr_x, cptr_coef, clr.intercept... \n", "numba-linreg-type predict_clr_numba_cast(x[0], clr.coef_, clr.in... \n", "python-linreg-custom predict_clr_python(z) \n", "cython-linreg predict_clr_cython(x[0], clr.coef_, clr.interc... \n", "python-linreg predict_clr_python_loop(z, coef, intercept) \n", "cffi-linreg-custom-float wrapped predict_clr_custom(x32) \n", "numba-linreg-type-numpy predict_clr_numba_numpy(x[0], clr.coef_, clr.i... \n", "cffi-linreg-custom wrapped predict_clr_custom(x) \n", "cffi-linreg-wrapped predict_clr(x, clr) \n", "numpy-linreg-numpy predict_clr_numpy(z, coef, clr.intercept_) \n", "onnxruntime-float64 predict_onnxrt(x.astype(numpy.float32)) \n", "onnxruntime-float32 predict_onnxrt(x32) \n", "numba-linreg-notype predict_clr_numba(z, clr.coef_, clr.intercept_) \n", "sklearn.predict clr.predict(z) \n", "\n", " run \n", "legend \n", "cffi-linreg-custom-float32 100 \n", "cffi-linreg-custom-float32-simd 100 \n", "cffi-linreg-custom 100 \n", "numba-linreg-type-float32 100 \n", "cython-linreg-type 100 \n", "numba-linreg-type-custom 100 \n", "cffi-linreg 100 \n", "numba-linreg-type 100 \n", "python-linreg-custom 100 \n", "cython-linreg 100 \n", "python-linreg 100 \n", "cffi-linreg-custom-float wrapped 100 \n", "numba-linreg-type-numpy 100 \n", "cffi-linreg-custom wrapped 100 \n", "cffi-linreg-wrapped 100 \n", "numpy-linreg-numpy 100 \n", "onnxruntime-float64 100 \n", "onnxruntime-float32 100 \n", "numba-linreg-notype 100 \n", "sklearn.predict 100 "]}, "execution_count": 72, "metadata": {}, "output_type": "execute_result"}], "source": ["import pandas\n", "df = pandas.DataFrame(data=memo_time)\n", "df = df.set_index(\"legend\").sort_values(\"average\")\n", "df"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On enl\u00e8ve quelques colonnes et on rappelle :\n", "\n", "* **cffi**: signifie optimis\u00e9 avec cffi\n", "* **custom**: pas de boucle mais la fonction ne peut pr\u00e9dire qu'une seule r\u00e9gression lin\u00e9aire\n", "* **float32**: utilise des float et non des double\n", "* **linreg**: r\u00e9gression lin\u00e9aire\n", "* **numba**: optimisation avec numba\n", "* **numpy**: optimisation avec numpy\n", "* **python**: pas de C, que du python\n", "* **simd**: optimis\u00e9 avec les instructions SIMD\n", "* **sklearn**: fonction sklearn.predict\n", "* **static**: la fonction utilise des variables statiques\n", "* **type**: la fonction est typ\u00e9e et ne fonctionne qu'avec un type pr\u00e9cis en entr\u00e9e.\n", "* **wrapped**: code optimis\u00e9 mais embabll\u00e9 dans une fonction Python qui elle ne l'est pas (les containers sont recr\u00e9\u00e9s \u00e0 chaque fois)"]}, {"cell_type": "code", "execution_count": 72, "metadata": {"scrolled": false}, "outputs": [{"data": {"text/html": ["
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
averagedeviationmin5max5runcode
legend
cffi-linreg-custom-float323.891910e-072.267541e-073.170000e-075.770000e-07100linreg_custom_float(cptr_x, cptr_out)
cffi-linreg-custom-float32-simd4.189890e-073.871792e-072.990000e-076.310000e-07100linreg_custom_float_simd(cptr_x, cptr_out)
cffi-linreg-custom4.665150e-078.519581e-073.150000e-077.150000e-07100linreg_custom(cptr_x, cptr_out)
numba-linreg-type-float327.070790e-072.686359e-075.650000e-071.249000e-06100predict_clr_numba_cast_float(x32, c32, i32)
cython-linreg-type7.213150e-073.991047e-075.330000e-071.443000e-06100predict_clr_cython_type(x[0], clr.coef_, clr.i...
numba-linreg-type-custom8.243540e-073.713608e-076.520000e-071.558000e-06100predict_clr_numba_cast_custom(x[0])
cffi-linreg8.313670e-077.080831e-074.160000e-071.519000e-06100lib.linreg(n, cptr_x, cptr_coef, clr.intercept...
numba-linreg-type9.482040e-074.114651e-077.590000e-071.678000e-06100predict_clr_numba_cast(x[0], clr.coef_, clr.in...
python-linreg-custom2.018942e-066.704544e-071.696000e-062.731000e-06100predict_clr_python(z)
cython-linreg2.706254e-061.597806e-061.920000e-067.194000e-06100predict_clr_cython(x[0], clr.coef_, clr.interc...
python-linreg3.539523e-061.306156e-062.681000e-066.164000e-06100predict_clr_python_loop(z, coef, intercept)
cffi-linreg-custom-float wrapped5.123886e-061.598363e-064.477000e-066.436000e-06100predict_clr_custom(x32)
numba-linreg-type-numpy5.147404e-061.775723e-064.374000e-065.996000e-06100predict_clr_numba_numpy(x[0], clr.coef_, clr.i...
cffi-linreg-custom wrapped5.274568e-061.823247e-064.422000e-067.773000e-06100predict_clr_custom(x)
cffi-linreg-wrapped7.519599e-062.343424e-066.201000e-061.041900e-05100predict_clr(x, clr)
numpy-linreg-numpy8.081947e-063.442724e-066.442000e-061.216200e-05100predict_clr_numpy(z, coef, clr.intercept_)
onnxruntime-float641.269215e-051.926911e-061.129500e-051.623200e-05100predict_onnxrt(x.astype(numpy.float32))
onnxruntime-float321.299773e-057.686900e-069.713000e-062.363700e-05100predict_onnxrt(x32)
numba-linreg-notype2.376539e-057.362380e-061.998900e-053.763900e-05100predict_clr_numba(z, clr.coef_, clr.intercept_)
sklearn.predict4.550096e-056.337585e-064.087300e-055.295400e-05100clr.predict(z)
\n", "
"], "text/plain": [" average deviation min5 \\\n", "legend \n", "cffi-linreg-custom-float32 3.891910e-07 2.267541e-07 3.170000e-07 \n", "cffi-linreg-custom-float32-simd 4.189890e-07 3.871792e-07 2.990000e-07 \n", "cffi-linreg-custom 4.665150e-07 8.519581e-07 3.150000e-07 \n", "numba-linreg-type-float32 7.070790e-07 2.686359e-07 5.650000e-07 \n", "cython-linreg-type 7.213150e-07 3.991047e-07 5.330000e-07 \n", "numba-linreg-type-custom 8.243540e-07 3.713608e-07 6.520000e-07 \n", "cffi-linreg 8.313670e-07 7.080831e-07 4.160000e-07 \n", "numba-linreg-type 9.482040e-07 4.114651e-07 7.590000e-07 \n", "python-linreg-custom 2.018942e-06 6.704544e-07 1.696000e-06 \n", "cython-linreg 2.706254e-06 1.597806e-06 1.920000e-06 \n", "python-linreg 3.539523e-06 1.306156e-06 2.681000e-06 \n", "cffi-linreg-custom-float wrapped 5.123886e-06 1.598363e-06 4.477000e-06 \n", "numba-linreg-type-numpy 5.147404e-06 1.775723e-06 4.374000e-06 \n", "cffi-linreg-custom wrapped 5.274568e-06 1.823247e-06 4.422000e-06 \n", "cffi-linreg-wrapped 7.519599e-06 2.343424e-06 6.201000e-06 \n", "numpy-linreg-numpy 8.081947e-06 3.442724e-06 6.442000e-06 \n", "onnxruntime-float64 1.269215e-05 1.926911e-06 1.129500e-05 \n", "onnxruntime-float32 1.299773e-05 7.686900e-06 9.713000e-06 \n", "numba-linreg-notype 2.376539e-05 7.362380e-06 1.998900e-05 \n", "sklearn.predict 4.550096e-05 6.337585e-06 4.087300e-05 \n", "\n", " max5 run \\\n", "legend \n", "cffi-linreg-custom-float32 5.770000e-07 100 \n", "cffi-linreg-custom-float32-simd 6.310000e-07 100 \n", "cffi-linreg-custom 7.150000e-07 100 \n", "numba-linreg-type-float32 1.249000e-06 100 \n", "cython-linreg-type 1.443000e-06 100 \n", "numba-linreg-type-custom 1.558000e-06 100 \n", "cffi-linreg 1.519000e-06 100 \n", "numba-linreg-type 1.678000e-06 100 \n", "python-linreg-custom 2.731000e-06 100 \n", "cython-linreg 7.194000e-06 100 \n", "python-linreg 6.164000e-06 100 \n", "cffi-linreg-custom-float wrapped 6.436000e-06 100 \n", "numba-linreg-type-numpy 5.996000e-06 100 \n", "cffi-linreg-custom wrapped 7.773000e-06 100 \n", "cffi-linreg-wrapped 1.041900e-05 100 \n", "numpy-linreg-numpy 1.216200e-05 100 \n", "onnxruntime-float64 1.623200e-05 100 \n", "onnxruntime-float32 2.363700e-05 100 \n", "numba-linreg-notype 3.763900e-05 100 \n", "sklearn.predict 5.295400e-05 100 \n", "\n", " code \n", "legend \n", "cffi-linreg-custom-float32 linreg_custom_float(cptr_x, cptr_out) \n", "cffi-linreg-custom-float32-simd linreg_custom_float_simd(cptr_x, cptr_out) \n", "cffi-linreg-custom linreg_custom(cptr_x, cptr_out) \n", "numba-linreg-type-float32 predict_clr_numba_cast_float(x32, c32, i32) \n", "cython-linreg-type predict_clr_cython_type(x[0], clr.coef_, clr.i... \n", "numba-linreg-type-custom predict_clr_numba_cast_custom(x[0]) \n", "cffi-linreg lib.linreg(n, cptr_x, cptr_coef, clr.intercept... \n", "numba-linreg-type predict_clr_numba_cast(x[0], clr.coef_, clr.in... \n", "python-linreg-custom predict_clr_python(z) \n", "cython-linreg predict_clr_cython(x[0], clr.coef_, clr.interc... \n", "python-linreg predict_clr_python_loop(z, coef, intercept) \n", "cffi-linreg-custom-float wrapped predict_clr_custom(x32) \n", "numba-linreg-type-numpy predict_clr_numba_numpy(x[0], clr.coef_, clr.i... \n", "cffi-linreg-custom wrapped predict_clr_custom(x) \n", "cffi-linreg-wrapped predict_clr(x, clr) \n", "numpy-linreg-numpy predict_clr_numpy(z, coef, clr.intercept_) \n", "onnxruntime-float64 predict_onnxrt(x.astype(numpy.float32)) \n", "onnxruntime-float32 predict_onnxrt(x32) \n", "numba-linreg-notype predict_clr_numba(z, clr.coef_, clr.intercept_) \n", "sklearn.predict clr.predict(z) "]}, "execution_count": 73, "metadata": {}, "output_type": "execute_result"}], "source": ["cols = [\"average\", \"deviation\", \"min5\", \"max5\", \"run\", \"code\"]\n", "df[cols]"]}, {"cell_type": "code", "execution_count": 73, "metadata": {}, "outputs": [{"name": "stderr", "output_type": "stream", "text": [":7: MatplotlibDeprecationWarning: The 'b' parameter of grid() has been renamed 'visible' since Matplotlib 3.5; support for the old name will be dropped two minor releases later.\n", " ax.grid(b=True, which=\"major\")\n", ":8: MatplotlibDeprecationWarning: The 'b' parameter of grid() has been renamed 'visible' since Matplotlib 3.5; support for the old name will be dropped two minor releases later.\n", " ax.grid(b=True, which=\"minor\");\n"]}, {"data": {"image/png": "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\n", "text/plain": ["
"]}, "metadata": {"needs_background": "light"}, "output_type": "display_data"}], "source": ["%matplotlib inline\n", "import matplotlib.pyplot as plt\n", "fig, ax = plt.subplots(1, 1, figsize=(14,6))\n", "df[[\"average\", \"deviation\"]].plot(kind=\"barh\", logx=True, ax=ax, xerr=\"deviation\",\n", " legend=False, fontsize=12, width=0.8)\n", "ax.set_ylabel(\"\")\n", "ax.grid(b=True, which=\"major\")\n", "ax.grid(b=True, which=\"minor\");"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Il manque \u00e0 ce comparatif le [GPU](https://en.wikipedia.org/wiki/Graphics_processing_unit) mais c'est un peu plus complexe \u00e0 mettre en oeuvre, il faut une carte [GPU](https://fr.wikipedia.org/wiki/Processeur_graphique) et la parall\u00e9lisation n'apporterait pas \u00e9norm\u00e9ment compte tenu de la faible dimension du probl\u00e8me."]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Pr\u00e9diction one-off et biais de mesure\n", "\n", "Le graphique pr\u00e9c\u00e9dent montre que la fonction ``predict`` de *scikit-learn* est la plus lente. La premi\u00e8re raison est que ce code est valable pour toutes les r\u00e9gresssions lin\u00e9aires alors que toutes les autres fonctions sont sp\u00e9cialis\u00e9es pour un seul mod\u00e8le. La seconde raison est que le code de *scikit-learn* est optimis\u00e9 pour le calcul de plusieurs pr\u00e9dictions \u00e0 la fois alors que toutes les autres fonctions n'en calcule qu'une seule (sc\u00e9nario dit *one-off*). On compare \u00e0 ce que donnerait unev version purement python et numpy."]}, {"cell_type": "code", "execution_count": 74, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([[214.72477745],\n", " [175.29091463]])"]}, "execution_count": 75, "metadata": {}, "output_type": "execute_result"}], "source": ["def predict_clr_python_loop_multi(x, coef, intercept): \n", " # On s'attend \u00e0 deux dimension.\n", " res = numpy.zeros((x.shape[0], 1))\n", " res[:, 0] = intercept\n", " for i in range(0, x.shape[0]):\n", " res[i, 0] += sum(a*b for a, b in zip(x[i, :], coef))\n", " return res\n", "\n", "predict_clr_python_loop_multi(diabetes_X_test[:2], clr.coef_, clr.intercept_)"]}, {"cell_type": "code", "execution_count": 75, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([[214.72477745],\n", " [175.29091463]])"]}, "execution_count": 76, "metadata": {}, "output_type": "execute_result"}], "source": ["def predict_clr_numpy_loop_multi(x, coef, intercept): \n", " # On s'attend \u00e0 deux dimension.\n", " res = numpy.ones((x.shape[0], 1)) * intercept\n", " res += x @ coef.reshape((len(coef), 1))\n", " return res\n", "\n", "predict_clr_numpy_loop_multi(diabetes_X_test[:2], clr.coef_, clr.intercept_)"]}, {"cell_type": "code", "execution_count": 76, "metadata": {}, "outputs": [{"data": {"text/plain": ["[214.724777447606, 175.29091463098356]"]}, "execution_count": 77, "metadata": {}, "output_type": "execute_result"}], "source": ["def predict_clr_numba_cast_multi(X, coef, intercept):\n", " return [predict_clr_numba_cast(x, coef, intercept) for x in X]\n", "\n", "predict_clr_numba_cast_multi(diabetes_X_test[:2], clr.coef_, clr.intercept_)"]}, {"cell_type": "code", "execution_count": 77, "metadata": {}, "outputs": [{"data": {"text/plain": ["[214.724777447606, 175.29091463098356]"]}, "execution_count": 78, "metadata": {}, "output_type": "execute_result"}], "source": ["def predict_clr_cython_type_multi(X, coef, intercept):\n", " return [predict_clr_cython_type(x, coef, intercept) for x in X]\n", "\n", "predict_clr_cython_type_multi(diabetes_X_test[:2], clr.coef_, clr.intercept_)"]}, {"cell_type": "code", "execution_count": 78, "metadata": {"scrolled": false}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["batch = 1\n", "Moyenne: 62.45 \u00b5s Ecart-type 33.39 \u00b5s (with 10 runs) in [41.71 \u00b5s, 119.15 \u00b5s]\n", "Moyenne: 6.43 \u00b5s Ecart-type 496.70 ns (with 10 runs) in [6.26 \u00b5s, 8.59 \u00b5s]\n", "Moyenne: 12.10 \u00b5s Ecart-type 8.26 \u00b5s (with 10 runs) in [8.20 \u00b5s, 26.70 \u00b5s]\n", "Moyenne: 1.99 \u00b5s Ecart-type 259.55 ns (with 10 runs) in [1.95 \u00b5s, 1.99 \u00b5s]\n", "Moyenne: 1.61 \u00b5s Ecart-type 103.91 ns (with 10 runs) in [1.59 \u00b5s, 1.63 \u00b5s]\n", "Moyenne: 19.38 \u00b5s Ecart-type 11.88 \u00b5s (with 10 runs) in [13.14 \u00b5s, 43.26 \u00b5s]\n", "batch = 10\n", "Moyenne: 73.96 \u00b5s Ecart-type 41.93 \u00b5s (with 10 runs) in [43.50 \u00b5s, 116.26 \u00b5s]\n", "Moyenne: 101.06 \u00b5s Ecart-type 3.73 \u00b5s (with 10 runs) in [98.16 \u00b5s, 111.41 \u00b5s]\n", "Moyenne: 19.06 \u00b5s Ecart-type 31.16 \u00b5s (with 10 runs) in [11.86 \u00b5s, 25.57 \u00b5s]\n", "Moyenne: 10.84 \u00b5s Ecart-type 5.26 \u00b5s (with 10 runs) in [8.34 \u00b5s, 22.70 \u00b5s]\n", "Moyenne: 10.46 \u00b5s Ecart-type 5.49 \u00b5s (with 10 runs) in [5.69 \u00b5s, 20.84 \u00b5s]\n", "Moyenne: 19.66 \u00b5s Ecart-type 25.05 \u00b5s (with 10 runs) in [12.23 \u00b5s, 34.34 \u00b5s]\n", "batch = 100\n", "Moyenne: 68.65 \u00b5s Ecart-type 26.04 \u00b5s (with 10 runs) in [46.99 \u00b5s, 119.00 \u00b5s]\n", "Moyenne: 740.30 \u00b5s Ecart-type 156.38 \u00b5s (with 10 runs) in [512.14 \u00b5s, 1.02 ms]\n", "Moyenne: 10.75 \u00b5s Ecart-type 3.89 \u00b5s (with 10 runs) in [8.72 \u00b5s, 16.79 \u00b5s]\n", "Moyenne: 94.32 \u00b5s Ecart-type 14.42 \u00b5s (with 10 runs) in [72.11 \u00b5s, 124.00 \u00b5s]\n", "Moyenne: 67.23 \u00b5s Ecart-type 31.05 \u00b5s (with 10 runs) in [43.72 \u00b5s, 135.94 \u00b5s]\n", "Moyenne: 91.28 \u00b5s Ecart-type 164.49 \u00b5s (with 10 runs) in [15.53 \u00b5s, 481.48 \u00b5s]\n", "batch = 200\n", "Moyenne: 68.82 \u00b5s Ecart-type 38.95 \u00b5s (with 10 runs) in [46.58 \u00b5s, 152.87 \u00b5s]\n", "Moyenne: 1.59 ms Ecart-type 497.08 \u00b5s (with 10 runs) in [1.09 ms, 2.98 ms]\n", "Moyenne: 11.66 \u00b5s Ecart-type 2.01 \u00b5s (with 10 runs) in [9.79 \u00b5s, 16.71 \u00b5s]\n", "Moyenne: 167.67 \u00b5s Ecart-type 37.37 \u00b5s (with 10 runs) in [133.64 \u00b5s, 240.53 \u00b5s]\n", "Moyenne: 102.09 \u00b5s Ecart-type 25.18 \u00b5s (with 10 runs) in [86.07 \u00b5s, 162.09 \u00b5s]\n", "Moyenne: 18.04 \u00b5s Ecart-type 8.31 \u00b5s (with 10 runs) in [15.00 \u00b5s, 34.57 \u00b5s]\n", "batch = 500\n", "Moyenne: 63.53 \u00b5s Ecart-type 20.92 \u00b5s (with 10 runs) in [50.94 \u00b5s, 116.69 \u00b5s]\n", "Moyenne: 3.22 ms Ecart-type 296.30 \u00b5s (with 10 runs) in [2.84 ms, 3.80 ms]\n", "Moyenne: 13.91 \u00b5s Ecart-type 4.58 \u00b5s (with 10 runs) in [11.80 \u00b5s, 26.71 \u00b5s]\n", "Moyenne: 410.88 \u00b5s Ecart-type 73.68 \u00b5s (with 10 runs) in [333.06 \u00b5s, 523.19 \u00b5s]\n", "Moyenne: 263.08 \u00b5s Ecart-type 117.22 \u00b5s (with 10 runs) in [211.75 \u00b5s, 444.83 \u00b5s]\n", "Moyenne: 22.28 \u00b5s Ecart-type 12.93 \u00b5s (with 10 runs) in [19.16 \u00b5s, 37.56 \u00b5s]\n", "batch = 1000\n", "Moyenne: 153.47 \u00b5s Ecart-type 43.85 \u00b5s (with 10 runs) in [125.94 \u00b5s, 229.51 \u00b5s]\n", "Moyenne: 5.52 ms Ecart-type 389.98 \u00b5s (with 10 runs) in [4.99 ms, 6.18 ms]\n", "Moyenne: 83.03 \u00b5s Ecart-type 22.52 \u00b5s (with 10 runs) in [73.67 \u00b5s, 95.91 \u00b5s]\n", "Moyenne: 702.77 \u00b5s Ecart-type 76.26 \u00b5s (with 10 runs) in [661.35 \u00b5s, 888.11 \u00b5s]\n", "Moyenne: 445.87 \u00b5s Ecart-type 53.38 \u00b5s (with 10 runs) in [420.78 \u00b5s, 548.59 \u00b5s]\n", "Moyenne: 27.48 \u00b5s Ecart-type 6.31 \u00b5s (with 10 runs) in [26.53 \u00b5s, 29.87 \u00b5s]\n", "batch = 2000\n", "Moyenne: 147.73 \u00b5s Ecart-type 19.47 \u00b5s (with 10 runs) in [132.10 \u00b5s, 187.47 \u00b5s]\n", "Moyenne: 83.71 \u00b5s Ecart-type 4.68 \u00b5s (with 10 runs) in [79.25 \u00b5s, 93.14 \u00b5s]\n", "Moyenne: 1.58 ms Ecart-type 216.25 \u00b5s (with 10 runs) in [1.32 ms, 1.97 ms]\n", "Moyenne: 47.31 \u00b5s Ecart-type 20.65 \u00b5s (with 10 runs) in [37.00 \u00b5s, 97.03 \u00b5s]\n", "batch = 3000\n", "Moyenne: 179.79 \u00b5s Ecart-type 45.35 \u00b5s (with 10 runs) in [144.02 \u00b5s, 310.74 \u00b5s]\n", "Moyenne: 92.27 \u00b5s Ecart-type 7.05 \u00b5s (with 10 runs) in [84.71 \u00b5s, 106.64 \u00b5s]\n", "Moyenne: 2.37 ms Ecart-type 267.39 \u00b5s (with 10 runs) in [1.99 ms, 2.91 ms]\n", "Moyenne: 50.69 \u00b5s Ecart-type 6.32 \u00b5s (with 10 runs) in [48.65 \u00b5s, 52.22 \u00b5s]\n", "batch = 4000\n", "Moyenne: 193.02 \u00b5s Ecart-type 28.74 \u00b5s (with 10 runs) in [173.71 \u00b5s, 211.52 \u00b5s]\n", "Moyenne: 100.06 \u00b5s Ecart-type 22.27 \u00b5s (with 10 runs) in [85.61 \u00b5s, 133.38 \u00b5s]\n", "Moyenne: 3.13 ms Ecart-type 296.38 \u00b5s (with 10 runs) in [2.73 ms, 3.54 ms]\n", "Moyenne: 64.67 \u00b5s Ecart-type 7.43 \u00b5s (with 10 runs) in [59.90 \u00b5s, 68.08 \u00b5s]\n", "batch = 5000\n", "Moyenne: 215.06 \u00b5s Ecart-type 46.52 \u00b5s (with 10 runs) in [196.06 \u00b5s, 411.19 \u00b5s]\n", "Moyenne: 110.91 \u00b5s Ecart-type 8.08 \u00b5s (with 10 runs) in [90.36 \u00b5s, 122.94 \u00b5s]\n", "Moyenne: 3.49 ms Ecart-type 212.13 \u00b5s (with 10 runs) in [3.30 ms, 4.04 ms]\n", "Moyenne: 78.86 \u00b5s Ecart-type 5.47 \u00b5s (with 10 runs) in [77.15 \u00b5s, 102.21 \u00b5s]\n", "batch = 10000\n", "Moyenne: 248.75 \u00b5s Ecart-type 64.14 \u00b5s (with 10 runs) in [192.57 \u00b5s, 425.01 \u00b5s]\n", "Moyenne: 116.55 \u00b5s Ecart-type 17.05 \u00b5s (with 10 runs) in [100.13 \u00b5s, 152.60 \u00b5s]\n", "Moyenne: 7.18 ms Ecart-type 420.77 \u00b5s (with 10 runs) in [6.62 ms, 8.15 ms]\n", "Moyenne: 153.30 \u00b5s Ecart-type 13.69 \u00b5s (with 10 runs) in [149.03 \u00b5s, 211.69 \u00b5s]\n", "batch = 20000\n", "Moyenne: 293.81 \u00b5s Ecart-type 19.49 \u00b5s (with 10 runs) in [283.46 \u00b5s, 364.31 \u00b5s]\n", "Moyenne: 147.12 \u00b5s Ecart-type 8.23 \u00b5s (with 10 runs) in [135.43 \u00b5s, 160.67 \u00b5s]\n", "Moyenne: 215.69 \u00b5s Ecart-type 14.46 \u00b5s (with 10 runs) in [204.68 \u00b5s, 262.99 \u00b5s]\n", "batch = 50000\n", "Moyenne: 1.00 ms Ecart-type 44.28 \u00b5s (with 10 runs) in [967.01 \u00b5s, 1.13 ms]\n", "Moyenne: 503.33 \u00b5s Ecart-type 13.21 \u00b5s (with 10 runs) in [491.66 \u00b5s, 551.81 \u00b5s]\n", "Moyenne: 1.86 ms Ecart-type 1.14 ms (with 10 runs) in [1.13 ms, 4.90 ms]\n", "batch = 75000\n", "Moyenne: 1.75 ms Ecart-type 153.53 \u00b5s (with 10 runs) in [1.56 ms, 1.94 ms]\n", "Moyenne: 663.38 \u00b5s Ecart-type 20.47 \u00b5s (with 10 runs) in [630.15 \u00b5s, 700.62 \u00b5s]\n", "Moyenne: 1.88 ms Ecart-type 173.99 \u00b5s (with 10 runs) in [1.65 ms, 2.14 ms]\n", "batch = 100000\n", "Moyenne: 2.56 ms Ecart-type 204.42 \u00b5s (with 10 runs) in [2.27 ms, 2.85 ms]\n", "Moyenne: 1.21 ms Ecart-type 113.75 \u00b5s (with 10 runs) in [1.04 ms, 1.44 ms]\n", "Moyenne: 2.98 ms Ecart-type 934.23 \u00b5s (with 10 runs) in [2.22 ms, 6.31 ms]\n", "batch = 150000\n", "Moyenne: 4.00 ms Ecart-type 188.08 \u00b5s (with 10 runs) in [3.78 ms, 4.46 ms]\n", "Moyenne: 2.92 ms Ecart-type 344.26 \u00b5s (with 10 runs) in [2.54 ms, 3.93 ms]\n", "Moyenne: 3.76 ms Ecart-type 308.56 \u00b5s (with 10 runs) in [3.26 ms, 4.52 ms]\n", "batch = 200000\n", "Moyenne: 5.73 ms Ecart-type 424.36 \u00b5s (with 10 runs) in [5.17 ms, 6.72 ms]\n", "Moyenne: 4.00 ms Ecart-type 606.67 \u00b5s (with 10 runs) in [3.50 ms, 6.04 ms]\n", "Moyenne: 5.44 ms Ecart-type 742.52 \u00b5s (with 10 runs) in [4.57 ms, 7.38 ms]\n", "batch = 300000\n", "Moyenne: 8.36 ms Ecart-type 1.26 ms (with 10 runs) in [7.78 ms, 13.52 ms]\n", "Moyenne: 5.37 ms Ecart-type 352.34 \u00b5s (with 10 runs) in [5.08 ms, 6.64 ms]\n", "Moyenne: 7.18 ms Ecart-type 680.24 \u00b5s (with 10 runs) in [6.69 ms, 8.83 ms]\n", "batch = 400000\n", "Moyenne: 11.49 ms Ecart-type 1.16 ms (with 10 runs) in [10.36 ms, 15.15 ms]\n", "Moyenne: 7.87 ms Ecart-type 709.04 \u00b5s (with 10 runs) in [7.18 ms, 9.70 ms]\n", "Moyenne: 10.51 ms Ecart-type 900.27 \u00b5s (with 10 runs) in [9.41 ms, 13.22 ms]\n", "batch = 500000\n", "Moyenne: 15.01 ms Ecart-type 1.90 ms (with 10 runs) in [12.99 ms, 20.81 ms]\n", "Moyenne: 11.02 ms Ecart-type 889.69 \u00b5s (with 10 runs) in [9.64 ms, 13.29 ms]\n", "Moyenne: 17.02 ms Ecart-type 2.13 ms (with 10 runs) in [14.72 ms, 22.19 ms]\n", "batch = 600000\n", "Moyenne: 21.19 ms Ecart-type 1.93 ms (with 10 runs) in [18.32 ms, 26.29 ms]\n", "Moyenne: 12.47 ms Ecart-type 964.03 \u00b5s (with 10 runs) in [11.00 ms, 14.31 ms]\n", "Moyenne: 18.04 ms Ecart-type 2.80 ms (with 10 runs) in [13.37 ms, 24.63 ms]\n"]}], "source": ["memo = []\n", "batch = [1, 10, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 10000,\n", " 20000, 50000, 75000, 100000, 150000, 200000, 300000, 400000,\n", " 500000, 600000]\n", "number = 10\n", "for i in batch:\n", " if i <= diabetes_X_test.shape[0]:\n", " mx = diabetes_X_test[:i]\n", " else:\n", " mxs = [diabetes_X_test] * (i // diabetes_X_test.shape[0] + 1)\n", " mx = numpy.vstack(mxs)\n", " mx = mx[:i]\n", "\n", " print(\"batch\", \"=\", i)\n", " repeat=20 if i >= 5000 else 100\n", " \n", " memo.append(timeexe(\"sklearn.predict %d\" % i, \"clr.predict(mx)\", \n", " repeat=repeat, number=number))\n", " memo[-1][\"batch\"] = i\n", " memo[-1][\"lib\"] = \"sklearn\"\n", " \n", " if i <= 1000:\n", " # tr\u00e8s lent\n", " memo.append(timeexe(\"python %d\" % i, \"predict_clr_python_loop_multi(mx, clr.coef_, clr.intercept_)\",\n", " repeat=20, number=number))\n", " memo[-1][\"batch\"] = i\n", " memo[-1][\"lib\"] = \"python\"\n", " \n", " memo.append(timeexe(\"numpy %d\" % i, \"predict_clr_numpy_loop_multi(mx, clr.coef_, clr.intercept_)\",\n", " repeat=repeat, number=number))\n", " memo[-1][\"batch\"] = i\n", " memo[-1][\"lib\"] = \"numpy\"\n", " \n", " if i <= 10000:\n", " # tr\u00e8s lent\n", " memo.append(timeexe(\"numba %d\" % i, \"predict_clr_numba_cast_multi(mx, clr.coef_, clr.intercept_)\",\n", " repeat=repeat, number=number))\n", " memo[-1][\"batch\"] = i\n", " memo[-1][\"lib\"] = \"numba\"\n", " \n", " if i <= 1000:\n", " # tr\u00e8s lent\n", " memo.append(timeexe(\"cython %d\" % i, \"predict_clr_cython_type_multi(mx, clr.coef_, clr.intercept_)\",\n", " repeat=repeat, number=number))\n", " memo[-1][\"batch\"] = i\n", " memo[-1][\"lib\"] = \"cython\"\n", " \n", " if ok_onnx:\n", " memo.append(timeexe(\"onnxruntime %d\" % i, \"predict_onnxrt(mx.astype(numpy.float32))\",\n", " repeat=repeat, number=number))\n", " memo[-1][\"batch\"] = i\n", " memo[-1][\"lib\"] = \"onnxruntime\""]}, {"cell_type": "code", "execution_count": 79, "metadata": {}, "outputs": [{"data": {"text/html": ["
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
libcythonnumbanumpyonnxruntimepythonsklearn
batch
10.0000020.0000020.0000120.0000190.0000060.000062
100.0000100.0000110.0000190.0000200.0001010.000074
1000.0000670.0000940.0000110.0000910.0007400.000069
2000.0001020.0001680.0000120.0000180.0015900.000069
5000.0002630.0004110.0000140.0000220.0032250.000064
10000.0004460.0007030.0000830.0000270.0055160.000153
2000NaN0.0015800.0000840.000047NaN0.000148
3000NaN0.0023710.0000920.000051NaN0.000180
4000NaN0.0031250.0001000.000065NaN0.000193
5000NaN0.0034900.0001110.000079NaN0.000215
10000NaN0.0071810.0001170.000153NaN0.000249
20000NaNNaN0.0001470.000216NaN0.000294
50000NaNNaN0.0005030.001863NaN0.001000
75000NaNNaN0.0006630.001879NaN0.001749
100000NaNNaN0.0012090.002980NaN0.002557
150000NaNNaN0.0029230.003762NaN0.004001
200000NaNNaN0.0040010.005440NaN0.005731
300000NaNNaN0.0053660.007180NaN0.008365
400000NaNNaN0.0078720.010510NaN0.011489
500000NaNNaN0.0110160.017021NaN0.015013
600000NaNNaN0.0124680.018040NaN0.021193
\n", "
"], "text/plain": ["lib cython numba numpy onnxruntime python sklearn\n", "batch \n", "1 0.000002 0.000002 0.000012 0.000019 0.000006 0.000062\n", "10 0.000010 0.000011 0.000019 0.000020 0.000101 0.000074\n", "100 0.000067 0.000094 0.000011 0.000091 0.000740 0.000069\n", "200 0.000102 0.000168 0.000012 0.000018 0.001590 0.000069\n", "500 0.000263 0.000411 0.000014 0.000022 0.003225 0.000064\n", "1000 0.000446 0.000703 0.000083 0.000027 0.005516 0.000153\n", "2000 NaN 0.001580 0.000084 0.000047 NaN 0.000148\n", "3000 NaN 0.002371 0.000092 0.000051 NaN 0.000180\n", "4000 NaN 0.003125 0.000100 0.000065 NaN 0.000193\n", "5000 NaN 0.003490 0.000111 0.000079 NaN 0.000215\n", "10000 NaN 0.007181 0.000117 0.000153 NaN 0.000249\n", "20000 NaN NaN 0.000147 0.000216 NaN 0.000294\n", "50000 NaN NaN 0.000503 0.001863 NaN 0.001000\n", "75000 NaN NaN 0.000663 0.001879 NaN 0.001749\n", "100000 NaN NaN 0.001209 0.002980 NaN 0.002557\n", "150000 NaN NaN 0.002923 0.003762 NaN 0.004001\n", "200000 NaN NaN 0.004001 0.005440 NaN 0.005731\n", "300000 NaN NaN 0.005366 0.007180 NaN 0.008365\n", "400000 NaN NaN 0.007872 0.010510 NaN 0.011489\n", "500000 NaN NaN 0.011016 0.017021 NaN 0.015013\n", "600000 NaN NaN 0.012468 0.018040 NaN 0.021193"]}, "execution_count": 80, "metadata": {}, "output_type": "execute_result"}], "source": ["dfb = pandas.DataFrame(memo)[[\"average\", \"lib\", \"batch\"]]\n", "piv = dfb.pivot(\"batch\", \"lib\", \"average\")\n", "piv"]}, {"cell_type": "code", "execution_count": 80, "metadata": {}, "outputs": [{"data": {"text/html": ["
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
libcythonnumbanumpyonnxruntimepythonsklearnave_cythonave_numbaave_numpyave_onnxruntimeave_pythonave_sklearn
batch
10.0000020.0000020.0000120.0000190.0000060.0000621.614900e-061.990900e-061.209790e-051.938460e-050.0000066.245340e-05
100.0000100.0000110.0000190.0000200.0001010.0000741.046460e-061.084320e-061.906010e-061.965810e-060.0000107.396440e-06
1000.0000670.0000940.0000110.0000910.0007400.0000696.722760e-079.431990e-071.075410e-079.127790e-070.0000076.865190e-07
2000.0001020.0001680.0000120.0000180.0015900.0000695.104525e-078.383455e-075.827850e-089.019900e-080.0000083.440995e-07
5000.0002630.0004110.0000140.0000220.0032250.0000645.261610e-078.217592e-072.781740e-084.455220e-080.0000061.270610e-07
10000.0004460.0007030.0000830.0000270.0055160.0001534.458687e-077.027674e-078.303090e-082.747640e-080.0000061.534708e-07
2000NaN0.0015800.0000840.000047NaN0.000148NaN7.899395e-074.185515e-082.365645e-08NaN7.386540e-08
3000NaN0.0023710.0000920.000051NaN0.000180NaN7.902492e-073.075760e-081.689707e-08NaN5.992867e-08
4000NaN0.0031250.0001000.000065NaN0.000193NaN7.813673e-072.501480e-081.616818e-08NaN4.825388e-08
5000NaN0.0034900.0001110.000079NaN0.000215NaN6.979748e-072.218220e-081.577170e-08NaN4.301210e-08
10000NaN0.0071810.0001170.000153NaN0.000249NaN7.180820e-071.165535e-081.533050e-08NaN2.487490e-08
20000NaNNaN0.0001470.000216NaN0.000294NaNNaN7.356025e-091.078465e-08NaN1.469057e-08
50000NaNNaN0.0005030.001863NaN0.001000NaNNaN1.006655e-083.725768e-08NaN2.000188e-08
75000NaNNaN0.0006630.001879NaN0.001749NaNNaN8.845087e-092.505991e-08NaN2.331396e-08
100000NaNNaN0.0012090.002980NaN0.002557NaNNaN1.208690e-082.980086e-08NaN2.556766e-08
150000NaNNaN0.0029230.003762NaN0.004001NaNNaN1.948814e-082.508106e-08NaN2.667062e-08
200000NaNNaN0.0040010.005440NaN0.005731NaNNaN2.000416e-082.720136e-08NaN2.865267e-08
300000NaNNaN0.0053660.007180NaN0.008365NaNNaN1.788538e-082.393301e-08NaN2.788189e-08
400000NaNNaN0.0078720.010510NaN0.011489NaNNaN1.967972e-082.627497e-08NaN2.872169e-08
500000NaNNaN0.0110160.017021NaN0.015013NaNNaN2.203297e-083.404131e-08NaN3.002589e-08
600000NaNNaN0.0124680.018040NaN0.021193NaNNaN2.077927e-083.006664e-08NaN3.532122e-08
\n", "
"], "text/plain": ["lib cython numba numpy onnxruntime python sklearn \\\n", "batch \n", "1 0.000002 0.000002 0.000012 0.000019 0.000006 0.000062 \n", "10 0.000010 0.000011 0.000019 0.000020 0.000101 0.000074 \n", "100 0.000067 0.000094 0.000011 0.000091 0.000740 0.000069 \n", "200 0.000102 0.000168 0.000012 0.000018 0.001590 0.000069 \n", "500 0.000263 0.000411 0.000014 0.000022 0.003225 0.000064 \n", "1000 0.000446 0.000703 0.000083 0.000027 0.005516 0.000153 \n", "2000 NaN 0.001580 0.000084 0.000047 NaN 0.000148 \n", "3000 NaN 0.002371 0.000092 0.000051 NaN 0.000180 \n", "4000 NaN 0.003125 0.000100 0.000065 NaN 0.000193 \n", "5000 NaN 0.003490 0.000111 0.000079 NaN 0.000215 \n", "10000 NaN 0.007181 0.000117 0.000153 NaN 0.000249 \n", "20000 NaN NaN 0.000147 0.000216 NaN 0.000294 \n", "50000 NaN NaN 0.000503 0.001863 NaN 0.001000 \n", "75000 NaN NaN 0.000663 0.001879 NaN 0.001749 \n", "100000 NaN NaN 0.001209 0.002980 NaN 0.002557 \n", "150000 NaN NaN 0.002923 0.003762 NaN 0.004001 \n", "200000 NaN NaN 0.004001 0.005440 NaN 0.005731 \n", "300000 NaN NaN 0.005366 0.007180 NaN 0.008365 \n", "400000 NaN NaN 0.007872 0.010510 NaN 0.011489 \n", "500000 NaN NaN 0.011016 0.017021 NaN 0.015013 \n", "600000 NaN NaN 0.012468 0.018040 NaN 0.021193 \n", "\n", "lib ave_cython ave_numba ave_numpy ave_onnxruntime ave_python \\\n", "batch \n", "1 1.614900e-06 1.990900e-06 1.209790e-05 1.938460e-05 0.000006 \n", "10 1.046460e-06 1.084320e-06 1.906010e-06 1.965810e-06 0.000010 \n", "100 6.722760e-07 9.431990e-07 1.075410e-07 9.127790e-07 0.000007 \n", "200 5.104525e-07 8.383455e-07 5.827850e-08 9.019900e-08 0.000008 \n", "500 5.261610e-07 8.217592e-07 2.781740e-08 4.455220e-08 0.000006 \n", "1000 4.458687e-07 7.027674e-07 8.303090e-08 2.747640e-08 0.000006 \n", "2000 NaN 7.899395e-07 4.185515e-08 2.365645e-08 NaN \n", "3000 NaN 7.902492e-07 3.075760e-08 1.689707e-08 NaN \n", "4000 NaN 7.813673e-07 2.501480e-08 1.616818e-08 NaN \n", "5000 NaN 6.979748e-07 2.218220e-08 1.577170e-08 NaN \n", "10000 NaN 7.180820e-07 1.165535e-08 1.533050e-08 NaN \n", "20000 NaN NaN 7.356025e-09 1.078465e-08 NaN \n", "50000 NaN NaN 1.006655e-08 3.725768e-08 NaN \n", "75000 NaN NaN 8.845087e-09 2.505991e-08 NaN \n", "100000 NaN NaN 1.208690e-08 2.980086e-08 NaN \n", "150000 NaN NaN 1.948814e-08 2.508106e-08 NaN \n", "200000 NaN NaN 2.000416e-08 2.720136e-08 NaN \n", "300000 NaN NaN 1.788538e-08 2.393301e-08 NaN \n", "400000 NaN NaN 1.967972e-08 2.627497e-08 NaN \n", "500000 NaN NaN 2.203297e-08 3.404131e-08 NaN \n", "600000 NaN NaN 2.077927e-08 3.006664e-08 NaN \n", "\n", "lib ave_sklearn \n", "batch \n", "1 6.245340e-05 \n", "10 7.396440e-06 \n", "100 6.865190e-07 \n", "200 3.440995e-07 \n", "500 1.270610e-07 \n", "1000 1.534708e-07 \n", "2000 7.386540e-08 \n", "3000 5.992867e-08 \n", "4000 4.825388e-08 \n", "5000 4.301210e-08 \n", "10000 2.487490e-08 \n", "20000 1.469057e-08 \n", "50000 2.000188e-08 \n", "75000 2.331396e-08 \n", "100000 2.556766e-08 \n", "150000 2.667062e-08 \n", "200000 2.865267e-08 \n", "300000 2.788189e-08 \n", "400000 2.872169e-08 \n", "500000 3.002589e-08 \n", "600000 3.532122e-08 "]}, "execution_count": 81, "metadata": {}, "output_type": "execute_result"}], "source": ["for c in piv.columns:\n", " piv[\"ave_\" + c] = piv[c] / piv.index\n", "piv"]}, {"cell_type": "code", "execution_count": 81, "metadata": {}, "outputs": [{"data": {"image/png": "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\n", "text/plain": ["
"]}, "metadata": {"needs_background": "light"}, "output_type": "display_data"}], "source": ["libs = list(c for c in piv.columns if \"ave_\" in c)\n", "ax = piv.plot(y=libs, logy=True, logx=True, figsize=(10, 5))\n", "ax.set_title(\"Evolution du temps de pr\u00e9diction selon la taille du batch\")\n", "ax.grid(True);"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Le minimum obtenu est pour $10^{-8} s$ soit 10 ns. Cela montre que la comparaisson pr\u00e9c\u00e9dente \u00e9tait incompl\u00e8te voire biais\u00e9e. Tout d\u00e9pend de l'usage qu'on fait de la fonction de pr\u00e9diction m\u00eame s'il sera toujours possible de d'\u00e9crire un code sp\u00e9cialis\u00e9 plus rapide que toute autre fonction g\u00e9n\u00e9rique. En g\u00e9n\u00e9ral, plus on reste du c\u00f4t\u00e9 Python, plus le programme est lent. Le nombre de passage de l'un \u00e0 l'autre, selon la fa\u00e7on dont il est fait ralenti aussi. En tenant compte de cela, le programme rouge sera plus lent que le vert."]}, {"cell_type": "code", "execution_count": 82, "metadata": {}, "outputs": [{"data": {"image/png": "iVBORw0KGgoAAAANSUhEUgAAAwMAAAE9CAIAAADYrymAAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAFiUAABYlAUlSJPAAACViSURBVHhe7d1Ldty6loThM5Bq1arhVFMD0mjcv5PwRNSuMZxiJkMSGMSLmXwAxP+taNzr3CBBEKS2Zfn4n38BAABGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScEAADGRScE3MDXn49/vn3+1S8CAMrohIAboBMCgBfRCQE3QCcEAC+iEwIAAOOiEwIAAOOiEwIAAOOiEwJa8vdTP+0zSf3AT+SHgvI/J/T19ffP58dvxdPHx8fnn79fKolZj5pHBCcLz1X8WaWKH2aaT7mcaXqer50x/MXouPAepCYK4D7O64SWr5+k6S1Yej9vNr9dP//YQSvey8DZyq1QbOOmN/PX38/8k/fhD8ZTZlTYqYTnKj5Q+YLSRNdP8MtnDH95NZI+CBhNc53Qj4/PXdqhr6kJ0gHphNCDUisU3bepzVz32Plp6h/W1LliE88WLPqPJHuIXz/j8gqDj+iDgPG02wk9vP8iCt5rdELoQ35jxj9NjFl8Xf/48/dLz8Djj8vC53HxcNij+vN7kuf3VvWL38L5FR+odMFyno9vCn/PZ/6DPX0ySc504xntKufDLn8tekQA93NNJ7RqSmZf01vPXrXvvozohNCf3M5M7Oj4kPwW//708SfSwaEWXclqVHjISfh5/myTVMHy12PvhunFoM8XI18+42zZf/1ZNIeJdxSAG2qqE5qFhYn3Wz06IXQovTVTGzo+YvEsVf5xc9geRB8LO6h+dVJ8oBIFuYf0RzA2fn1bzvhj0QsF6IOAkTTYCS1fT2++knIv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\n", "text/plain": [""]}, "execution_count": 83, "metadata": {}, "output_type": "execute_result"}], "source": ["from pyquickhelper.helpgen import NbImage\n", "NbImage(\"pycpp.png\")"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Ces r\u00e9sultats sont d'une fa\u00e7on g\u00e9n\u00e9rale assez volatile car le temps de calcul est enrob\u00e9 dans plusieurs fonctions Python qui rendent une mesure pr\u00e9cise difficile. Il reste n\u00e9anmoins une bonne id\u00e9e des ordres de grandeurs."]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Random Forest\n", "\n", "On reproduit les m\u00eames r\u00e9sultats pour une random forest mais la r\u00e9\u00e9criture n'est plus aussi simple qu'une r\u00e9gression lin\u00e9aire."]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Une pr\u00e9diction \u00e0 la fois"]}, {"cell_type": "code", "execution_count": 83, "metadata": {}, "outputs": [], "source": ["from sklearn.datasets import load_diabetes\n", "diabetes = load_diabetes()\n", "diabetes_X_train = diabetes.data[:-20]\n", "diabetes_X_test = diabetes.data[-20:]\n", "diabetes_y_train = diabetes.target[:-20]\n", "diabetes_y_test = diabetes.target[-20:]"]}, {"cell_type": "code", "execution_count": 84, "metadata": {}, "outputs": [{"data": {"text/plain": ["RandomForestRegressor(n_estimators=10)"]}, "execution_count": 85, "metadata": {}, "output_type": "execute_result"}], "source": ["from sklearn.ensemble import RandomForestRegressor\n", "rf = RandomForestRegressor(n_estimators=10)\n", "rf.fit(diabetes_X_train, diabetes_y_train)"]}, {"cell_type": "code", "execution_count": 85, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Moyenne: 980.23 \u00b5s Ecart-type 60.93 \u00b5s (with 20 runs) in [937.55 \u00b5s, 1.11 ms]\n"]}], "source": ["memo_time = []\n", "x = diabetes_X_test[:1]\n", "memo_time.append(timeexe(\"sklearn-rf\", \"rf.predict(x)\", repeat=100, number=20))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["C'est beaucoup plus long que la r\u00e9gression lin\u00e9aire. On essaye avec *onnx*."]}, {"cell_type": "code", "execution_count": 86, "metadata": {"scrolled": false}, "outputs": [], "source": ["if ok_onnx:\n", " onnxrf_model = convert_sklearn(\n", " rf, 'model', [('input', FloatTensorType([None, clr.coef_.shape[0]]))],\n", " target_opset=11)\n", " onnxrf_model.ir_version = 6\n", " save_model(onnxrf_model, 'model_rf.onnx') \n", " model_onnx = onnx.load('model_rf.onnx')"]}, {"cell_type": "code", "execution_count": 87, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Input: NodeArg(name='input', type='tensor(float)', shape=[None, 10])\n", "Output: NodeArg(name='variable', type='tensor(float)', shape=[None, 1])\n", "[array([[243.00002]], dtype=float32)]\n", "Moyenne: 14.36 \u00b5s Ecart-type 4.18 \u00b5s (with 20 runs) in [11.75 \u00b5s, 22.22 \u00b5s]\n"]}], "source": ["if ok_onnx:\n", " sess = onnxruntime.InferenceSession(\"model_rf.onnx\")\n", " for i in sess.get_inputs():\n", " print('Input:', i)\n", " for o in sess.get_outputs():\n", " print('Output:', o)\n", " \n", " def predict_onnxrt_rf(x): \n", " return sess.run([\"variable\"], {'input': x})\n", "\n", " print(predict_onnxrt_rf(x.astype(numpy.float32)))\n", " memo_time.append(timeexe(\"onnx-rf\", \"predict_onnxrt_rf(x.astype(numpy.float32))\",\n", " repeat=100, number=20))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["C'est beaucoup plus rapide."]}, {"cell_type": "code", "execution_count": 88, "metadata": {}, "outputs": [{"data": {"text/html": ["
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
averagedeviationfirstfirst3last3repeatmin5max5coderun
legend
onnx-rf0.0000140.0000040.0000470.0000270.0000141000.0000120.000022predict_onnxrt_rf(x.astype(numpy.float32))20
sklearn-rf0.0009800.0000610.0013080.0010870.0010751000.0009380.001106rf.predict(x)20
\n", "
"], "text/plain": [" average deviation first first3 last3 repeat \\\n", "legend \n", "onnx-rf 0.000014 0.000004 0.000047 0.000027 0.000014 100 \n", "sklearn-rf 0.000980 0.000061 0.001308 0.001087 0.001075 100 \n", "\n", " min5 max5 code \\\n", "legend \n", "onnx-rf 0.000012 0.000022 predict_onnxrt_rf(x.astype(numpy.float32)) \n", "sklearn-rf 0.000938 0.001106 rf.predict(x) \n", "\n", " run \n", "legend \n", "onnx-rf 20 \n", "sklearn-rf 20 "]}, "execution_count": 89, "metadata": {}, "output_type": "execute_result"}], "source": ["import pandas\n", "df2 = pandas.DataFrame(data=memo_time)\n", "df2 = df2.set_index(\"legend\").sort_values(\"average\")\n", "df2"]}, {"cell_type": "code", "execution_count": 89, "metadata": {}, "outputs": [{"name": "stderr", "output_type": "stream", "text": [":5: MatplotlibDeprecationWarning: The 'b' parameter of grid() has been renamed 'visible' since Matplotlib 3.5; support for the old name will be dropped two minor releases later.\n", " ax.grid(b=True, which=\"major\")\n", ":6: MatplotlibDeprecationWarning: The 'b' parameter of grid() has been renamed 'visible' since Matplotlib 3.5; support for the old name will be dropped two minor releases later.\n", " ax.grid(b=True, which=\"minor\");\n"]}, {"data": {"image/png": "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MH8Hc4Zrd5K9SW7N6qV8D07y0rVvGGP8bpJ3JvlAVT1gv3U3JnlqkpdlNVjdmNU7BX7LGOP2rN798PIkX0zyzKze2AIAAGDp3es34cYYn0jy2HVWnbff+16R5BUbrLsqyRM3qH9xkos3WLeS1dvFr122f+0b8s2XIO5f47x1lr0xyRs32gYAAGAWluofHwMAACwTgQsAAKCJwAUAANBE4AIAAGgicAEAADQRuAAAAJoIXAAAAE0ELgAAgCYCFwAAQBOBCwAAoInABQAA0ETgAgAAaCJwAQAANBG4AAAAmghcAAAATQQuAACAJgIXAABAE4ELAACgicAFAADQROACAABoInABAAA0EbgAAACaCFwAAABNBC4AAIAmAhcAAEATgQsAAKCJwAUAANBE4AIAAGgicAEAADQRuAAAAJoIXAAAAF3GGB4HeJx00kljGldeeeVU28+qBv126nFatnEvUr9b3Uvn/jpqz6rmtHUW6TPDxnbycVq2sS9Kv9tpDu6obw4+NEmuHhvkCWe4AAAAmghcAAAATQQuAACAJgIXAABAE4ELAACgicAFAADQROACAABoInABAAA0EbgAAACaCFwAAABNBC4AAIAmAhcAAEATgQsAAKCJwAUAANBE4AIAAGgicAEAADQRuAAAAJoIXAAAAE12zbuBRffJz38px5/77k1vf84Jd+esKbafVY1FdcPeM+bdAgAAtHGGCwAAoInABQAA0ETgAgAAaCJwAQAANBG4AAAAmghcAAAATQQuAACAJgIXAABAE4ELAACgicAFAADQROACAABoInABAAA0EbgAAACaCFwAAABNBC4AAIAmAhcAAEATgQsAAKCJwAUAANBE4AIAAGgicAEAADQRuAAAAJoIXAAAAE0ELgAAgCYCFwAAQBOBCwAAoInABQAA0ETgAgAAaCJwAQAANBG4AAAAmghcAAAATQQuAACAJgIXAABAkx0ZuKrqUVV1XVXdXlUvmnc/AADA9rRr3g3MyYuTXDnGOHHejQAAANvXjjrDVVX7AubDklw/z14AAIDtrzVwVdV3V9VKVd1WVddX1VMmy99YVRdX1bsnl/VdVVXftWa7UVUvqKp/m2x7cVXVZN3rquoda957QVV9cN/6dXq4oapeUlWfSHJnVX0oyalJ/qSq7qiqR3b+DAAAgJ2r7ZLCqrpPkncleUOSH09ySpK/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\n", "text/plain": ["
"]}, "metadata": {"needs_background": "light"}, "output_type": "display_data"}], "source": ["fig, ax = plt.subplots(1, 1, figsize=(14,4))\n", "df2[[\"average\", \"deviation\"]].plot(kind=\"barh\", logx=True, ax=ax, xerr=\"deviation\",\n", " legend=False, fontsize=12, width=0.8)\n", "ax.set_ylabel(\"\")\n", "ax.grid(b=True, which=\"major\")\n", "ax.grid(b=True, which=\"minor\");"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Pr\u00e9diction en batch"]}, {"cell_type": "code", "execution_count": 90, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["batch = 1\n", "Moyenne: 1.11 ms Ecart-type 145.19 \u00b5s (with 10 runs) in [1.03 ms, 1.54 ms]\n", "Moyenne: 15.70 \u00b5s Ecart-type 13.36 \u00b5s (with 10 runs) in [11.20 \u00b5s, 55.77 \u00b5s]\n", "batch = 10\n", "Moyenne: 1.14 ms Ecart-type 162.36 \u00b5s (with 10 runs) in [952.57 \u00b5s, 1.51 ms]\n", "Moyenne: 25.55 \u00b5s Ecart-type 9.43 \u00b5s (with 10 runs) in [17.37 \u00b5s, 42.15 \u00b5s]\n", "batch = 100\n", "Moyenne: 1.09 ms Ecart-type 80.51 \u00b5s (with 10 runs) in [1.01 ms, 1.31 ms]\n", "Moyenne: 38.04 \u00b5s Ecart-type 17.20 \u00b5s (with 10 runs) in [32.02 \u00b5s, 89.62 \u00b5s]\n", "batch = 200\n", "Moyenne: 1.42 ms Ecart-type 126.30 \u00b5s (with 10 runs) in [1.15 ms, 1.71 ms]\n", "Moyenne: 82.17 \u00b5s Ecart-type 56.27 \u00b5s (with 10 runs) in [43.86 \u00b5s, 213.17 \u00b5s]\n", "batch = 500\n", "Moyenne: 1.79 ms Ecart-type 543.34 \u00b5s (with 10 runs) in [1.31 ms, 3.18 ms]\n", "Moyenne: 130.31 \u00b5s Ecart-type 30.45 \u00b5s (with 10 runs) in [85.15 \u00b5s, 190.08 \u00b5s]\n", "batch = 1000\n", "Moyenne: 1.53 ms Ecart-type 93.12 \u00b5s (with 10 runs) in [1.42 ms, 1.70 ms]\n", "Moyenne: 249.60 \u00b5s Ecart-type 23.96 \u00b5s (with 10 runs) in [232.24 \u00b5s, 312.27 \u00b5s]\n", "batch = 2000\n", "Moyenne: 2.09 ms Ecart-type 149.23 \u00b5s (with 10 runs) in [1.89 ms, 2.33 ms]\n", "Moyenne: 393.37 \u00b5s Ecart-type 165.01 \u00b5s (with 10 runs) in [283.40 \u00b5s, 734.87 \u00b5s]\n", "batch = 3000\n", "Moyenne: 2.77 ms Ecart-type 921.32 \u00b5s (with 10 runs) in [2.24 ms, 5.40 ms]\n", "Moyenne: 432.57 \u00b5s Ecart-type 16.08 \u00b5s (with 10 runs) in [422.71 \u00b5s, 479.76 \u00b5s]\n", "batch = 4000\n", "Moyenne: 2.96 ms Ecart-type 331.99 \u00b5s (with 10 runs) in [2.63 ms, 3.69 ms]\n", "Moyenne: 1.04 ms Ecart-type 485.53 \u00b5s (with 10 runs) in [598.92 \u00b5s, 2.38 ms]\n", "batch = 5000\n", "Moyenne: 3.27 ms Ecart-type 348.48 \u00b5s (with 10 runs) in [3.00 ms, 4.16 ms]\n", "Moyenne: 996.95 \u00b5s Ecart-type 207.84 \u00b5s (with 10 runs) in [767.12 \u00b5s, 1.47 ms]\n", "batch = 10000\n", "Moyenne: 5.26 ms Ecart-type 404.81 \u00b5s (with 10 runs) in [4.96 ms, 6.34 ms]\n", "Moyenne: 1.75 ms Ecart-type 317.18 \u00b5s (with 10 runs) in [1.34 ms, 2.13 ms]\n", "batch = 20000\n", "Moyenne: 10.52 ms Ecart-type 1.11 ms (with 10 runs) in [9.21 ms, 13.42 ms]\n", "Moyenne: 4.40 ms Ecart-type 522.54 \u00b5s (with 10 runs) in [3.52 ms, 5.43 ms]\n", "batch = 50000\n", "Moyenne: 24.33 ms Ecart-type 2.90 ms (with 10 runs) in [21.27 ms, 29.83 ms]\n", "Moyenne: 8.21 ms Ecart-type 1.31 ms (with 10 runs) in [7.32 ms, 11.74 ms]\n", "batch = 75000\n", "Moyenne: 31.54 ms Ecart-type 251.81 \u00b5s (with 10 runs) in [31.19 ms, 32.06 ms]\n", "Moyenne: 12.22 ms Ecart-type 908.21 \u00b5s (with 10 runs) in [11.38 ms, 14.24 ms]\n", "batch = 100000\n", "Moyenne: 42.05 ms Ecart-type 745.44 \u00b5s (with 10 runs) in [41.22 ms, 43.35 ms]\n", "Moyenne: 16.17 ms Ecart-type 1.01 ms (with 10 runs) in [14.98 ms, 17.54 ms]\n"]}], "source": ["memo = []\n", "batch = [1, 10, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 10000,\n", " 20000, 50000, 75000, 100000, 150000, 200000, 300000, 400000,\n", " 500000, 600000]\n", "number = 10\n", "repeat = 10\n", "for i in batch[:15]:\n", " if i <= diabetes_X_test.shape[0]:\n", " mx = diabetes_X_test[:i]\n", " else:\n", " mxs = [diabetes_X_test] * (i // diabetes_X_test.shape[0] + 1)\n", " mx = numpy.vstack(mxs)\n", " mx = mx[:i]\n", "\n", " print(\"batch\", \"=\", i)\n", " \n", " memo.append(timeexe(\"sklearn.predict %d\" % i, \"rf.predict(mx)\", \n", " repeat=repeat, number=number))\n", " memo[-1][\"batch\"] = i\n", " memo[-1][\"lib\"] = \"sklearn\"\n", " \n", " if ok_onnx:\n", " memo.append(timeexe(\"onnxruntime %d\" % i, \n", " \"predict_onnxrt_rf(mx.astype(numpy.float32))\",\n", " repeat=repeat, number=number))\n", " memo[-1][\"batch\"] = i\n", " memo[-1][\"lib\"] = \"onnxruntime\""]}, {"cell_type": "code", "execution_count": 91, "metadata": {}, "outputs": [{"data": {"image/png": "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\n", "text/plain": ["
"]}, "metadata": {"needs_background": "light"}, "output_type": "display_data"}], "source": ["dfbrf = pandas.DataFrame(memo)[[\"average\", \"lib\", \"batch\"]]\n", "pivrf = dfbrf.pivot(\"batch\", \"lib\", \"average\")\n", "for c in pivrf.columns:\n", " pivrf[\"ave_\" + c] = pivrf[c] / pivrf.index\n", "libs = list(c for c in pivrf.columns if \"ave_\" in c)\n", "ax = pivrf.plot(y=libs, logy=True, logx=True, figsize=(10, 5))\n", "ax.set_title(\"Evolution du temps de pr\u00e9diction selon la taille du batch\\nrandom forest\")\n", "ax.grid(True);"]}, {"cell_type": "code", "execution_count": 92, "metadata": {}, "outputs": [], "source": []}], "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"codemirror_mode": {"name": "ipython", "version": 3}, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.5"}}, "nbformat": 4, "nbformat_minor": 2}