{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Optimisation of (pseudo-) random number generation\n", "\n", "This notebook is the demonstration of the code used in production in the `densify_Bragg` application.\n", "\n", "This story started with 5 lines of `numpy` to generate images made of pixels having each their own mean and standard deviation. Images are composed of several mega-pixels and there are thousands of them. The 5 lines of Python are providing a proper result but with its 10 frames per seconds, it is far too slow, and the client expects at least 100 to process the image-stack reasonably fast.\n", "\n", "Since this is intended to be production code, hence widely distributed, my favorite tool, the GPU gets discarded. So I am stucked with Python and some tools already in use within the project: Cython and a C/C++ compiler. Since the code needs to run on any platform (windows, macos and linux to the least) and on many achitectures x86, amd64, ppc64le and arm64 among other. Other fancy tools like OpenMP are neither an option since it is not supported on macos.\n", "\n", "Naively, I started to rewrite this function in C, sure that this would be enough ... how disapointed was I when I realized it was even slower than the initial Python !\n", "\n", "This triggered some rationnal benchmarking on random number generation that I would like to share with you.\n", "\n", "First of all, all pseudo random numbers, like the one obtained from a normal distribution are derived from an uniform distribution, and one of the most commonly used generator is based on the Mesenne-Twister. While is not cryptographically strong, it is neverthless enough for our needs.\n", "\n", "*Disclaimer:* I am not a guru in C nor in C++, so I would be pleased if you can help me fixing some code if you believe it is sub-optimal.\n", "\n", "## Some reference `Numpy` code and figures" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3.12.3 | packaged by Anaconda, Inc. | (main, Apr 19 2024, 16:50:38) [GCC 11.2.0]\n" ] } ], "source": [ "%matplotlib inline\n", "import sys, time, random\n", "start_time = time.perf_counter()\n", "print(sys.version)" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import numpy\n", "from matplotlib.pyplot import subplots\n", "\n", "#This is the size of one image:\n", "shape = (2167,2070)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Reference timing from numpy: Uniform\n", "38.6 ms ± 519 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", "Reference timing from numpy: Normal\n", "122 ms ± 1.37 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkIAAAGzCAYAAADDgXghAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQAAMrZJREFUeJzt3XlYVfW+x/EPgwwOG3IAHEgccjY5YhJNZJFUVFp2HDLFKW+GlpI5lEe0SbMJS83Km5hHc+hmt8QwL6Y+JTmQ3KuWNlna8WzQpwBzYFz3j/OwjltQwBiC3/v1PPt52r/1XWt9928v2h8Xa23cLMuyBAAAYCD32m4AAACgthCEAACAsQhCAADAWAQhAABgLIIQAAAwFkEIAAAYiyAEAACMRRACAADGIggBAABjEYSAyzRq1CiFhIS4jP3+++8aN26cgoKC5ObmpsmTJ9dKb3+Um5ub5syZYz9PSkqSm5ubfvrpp2rf94Xz+tNPP8nNzU0vvfRSte9bkubMmSM3N7ca2deF6svxA9QlBCHUayUfaidPnixzeY8ePXTzzTdX2f6ef/55JSUlacKECVq5cqVGjBhRZduua86cOaM5c+Zo27Zttd1KKX/W3ura8ePm5iY3Nze9/PLLpZaVhOe9e/fWQmdAxXnWdgNAXfX222+ruLjYZWzr1q269tprlZCQUEtdVY8RI0Zo6NCh8vb2rvA6Z86c0dy5cyWpUmGzrHmtapfqbdasWZoxY0a17v9i6urx8+KLL2rChAlq2LBhbbcCVBpnhIDL1KBBg1LBICsrS/7+/lW2j8LCQuXn51fZ9i6Xh4eHfHx8qvVXRqdPn5ZU9rzWJE9PT/n4+NTKvit6/Jw7d67aw2JFhYaGKjMzU0uXLq3tVoDLQhACzrNt2za5ublp3bp1eu6559SmTRv5+Pjo1ltv1ffff+9Se/61LCXrHTlyRMnJyfavDEquqcnKytLYsWMVGBgoHx8f9erVSytWrHDZ3vnXwiQmJqpDhw7y9vbW119/bf+K79tvv9WDDz4oPz8/tWjRQn/7299kWZaOHTumAQMGyOFwKCgoqMxfVZQlLy9PU6ZMUYsWLdSkSRPdc889+uWXX0rVlXWN0N69exUdHa3mzZvL19dX7dq105gxY+zX0qJFC0nS3Llz7fkoue5o1KhRaty4sX744QfdeeedatKkiYYPH15qXi/06quvqm3btvL19VVkZKQOHDjgsvzmm28u8+zT+dssr7eyrhEqLCzUM888Y78nISEhevLJJ5WXl+dSFxISorvuukuff/65+vbtKx8fH7Vv317vvvtuma+nxKWOn5Jla9as0axZs9S6dWs1bNhQubm5kqT169crLCxMvr6+at68uR588EH94x//KPX6GzdurKNHj+quu+5S48aN1bp1ay1evFiStH//ft1yyy1q1KiR2rZtq9WrV1+y3/Ndf/31uuWWW7RgwQKdPXv2krUVeX8k15+FxYsXq3379mrYsKH69++vY8eOybIsPfPMM2rTpo18fX01YMAA/frrry7bLHkvPv30U4WGhsrHx0fdunXTBx98YNf8+OOPcnNz06uvvlqqp507d8rNzU3vvfdehecCdRNBCCjD/PnztWHDBk2dOlUzZ87Ul19+aX9Ql6Vr165auXKlmjdvrtDQUK1cuVIrV65UixYtdPbsWd18881auXKlhg8frhdffFF+fn4aNWqUFi5cWGpby5cv1+uvv67x48fr5ZdfVtOmTe1lQ4YMUXFxsebPn6/w8HA9++yzSkxM1G233abWrVvrhRdeUMeOHTV16lTt2LGj3Nc5btw4JSYmqn///po/f74aNGigmJiYctfLyspS//799dNPP2nGjBl6/fXXNXz4cH355ZeSpBYtWuiNN96QJN177732fNx33332NgoLCxUdHa2AgAC99NJLGjRo0CX3+e677+q1115TXFycZs6cqQMHDuiWW25RZmZmuf2eryK9XWjcuHGaPXu2evfurVdffVWRkZGaN2+ehg4dWqr2+++/1/3336/bbrtNL7/8sq644gqNGjVKBw8evOj2L3X8lHjmmWeUnJysqVOn6vnnn5eXl5eSkpI0ePBgeXh4aN68eXrooYf0wQcf6IYbblB2drbLPoqKinTHHXcoODhYCxYsUEhIiCZOnKikpCTdfvvt6tOnj1544QU1adJEI0eO1JEjRyo8p3PmzFFmZqY9r1Vl1apVWrJkiSZNmqTHH39c27dv1+DBgzVr1iylpKRo+vTpGj9+vD7++GNNnTq11PrfffedhgwZojvuuEPz5s2Tp6en/vrXv2rLli2SpPbt2+v666/XqlWrytx3kyZNNGDAgCp9TfgTsoB6LCEhwZJknThxoszl3bt3tyIjI+3nn332mSXJ6tq1q5WXl2ePL1y40JJk7d+/3x6LjY212rZt67K9tm3bWjExMS5jiYmJliTr73//uz2Wn59vRUREWI0bN7Zyc3Mty7KsI0eOWJIsh8NhZWVllfk6xo8fb48VFhZabdq0sdzc3Kz58+fb47/99pvl6+trxcbGXnJuMjIyLEnWI4884jL+wAMPWJKshIQEe2z58uWWJOvIkSOWZVnWhg0bLEnWnj17Lrr9EydOlNpOidjYWEuSNWPGjDKXnT+vJfPi6+tr/fLLL/b4rl27LEnWlClT7LHIyEiX9/Ni27xUbyVzXaJknsaNG+dSN3XqVEuStXXrVnusbdu2liRrx44d9lhWVpbl7e1tPf7446X2daGyjp+SY7J9+/bWmTNn7PH8/HwrICDA6tGjh3X27Fl7fOPGjZYka/bs2S6vX5L1/PPP22Mlx4mbm5u1Zs0ae/zQoUMXnZsLSbLi4uIsy7Ksfv36WUFBQXaPJcfM+cdIRd+fkve8RYsWVnZ2tj0+c+ZMS5LVq1cvq6CgwB4fNmyY5eXlZZ07d84eK3kv/uu//ssey8nJsVq2bGn95S9/scfefPNNS5L1zTff2GP5+flW8+bNy/0ZQv3AGSGgDKNHj5aXl5f9/MYbb5T0r1PplbVp0yYFBQVp2LBh9liDBg306KOP6vfff9f27dtd6gcNGuRyJuB848aNs//bw8NDffr0kWVZGjt2rD3u7++vzp07l9vrpk2bJEmPPvqoy3hFbtkuuY5l48aNKigoKLf+YiZMmFDh2oEDB6p169b28759+yo8PNx+HdWlZPvx8fEu448//rgkKTk52WW8W7du9vEi/esMVEXej/LExsbK19fXfr53715lZWXpkUcecbmmKSYmRl26dCnVl+R6/JQcJ40aNdLgwYPt8c6dO8vf37/S/c6ZM0dOp7NKrxX661//Kj8/P/t5eHi4JOnBBx+Up6eny3h+fn6pXwm2atVK9957r/3c4XBo5MiR2rdvn5xOpyRp8ODB8vHxcTkrtHnzZp08eVIPPvhglb0W/HkRhGC8si4AvvLKK12eX3HFFZKk3377rdLb//nnn3XVVVfJ3d31x61r16728vO1a9fuotu6sC8/Pz/5+PioefPmpcbL6/Xnn3+Wu7u7OnTo4DLeuXPnS64nSZGRkRo0aJDmzp2r5s2ba8CAAVq+fHmpa2YuxdPTU23atKlw/VVXXVVqrFOnTtX+3UYl89SxY0eX8aCgIPn7+5d6/y58j6R/HT+Xc+yc78LjomS/Zb1fXbp0KdWXj49PqYDt5+enNm3alPoZqMjxc6GbbrpJ/fr1q9C1QhVV1vEuScHBwWWOX9hzx44dS722Tp06SZJ93Pj7++vuu+92uS5q1apVat26tW655ZY//iLwp0cQQr1W8i/li/2P+cyZM2XeIeTh4VFmvWVZVdfcRZz/r/4LldVXbfTq5uam999/X2lpaZo4caL+8Y9/aMyYMQoLC9Pvv/9eoW14e3uXCodV0VdZioqKqm3bF6qu9+NSx0VFXKyvquw3ISFBTqdTb775ZpnLK/v+1ETPkjRy5Ej9+OOP2rlzp06dOqWPPvpIw4YNq/LjE39OvMuo19q2bStJOnz4cKllZ86c0bFjx+ya6uzhu+++K3W786FDh1x6rGlt27ZVcXGxfvjhB5fxsubqYq699lo999xz2rt3r1atWqWDBw9qzZo1kioeHCrqu+++KzX27bffutxtdMUVV5S6SFgqfdatMr2VzNOF+8/MzFR2dnatvn9S2e/X4cOHa6WvyMhI3XzzzXrhhRfK/MdHRd+fqvL999+XCkfffvutJLkcN7fffrtatGihVatWacOGDTpz5syf/sssUXUIQqjXbr31Vnl5eemNN94oFUTeeustFRYW6o477qjWHu688045nU6tXbvWHissLNTrr7+uxo0bKzIyslr3fzElr/u1115zGU9MTCx33d9++63UB0xoaKgk2b8eK/lyvbI++C7Hhx9+6HINyO7du7Vr1y6X969Dhw46dOiQTpw4YY/97//+r7744guXbVWmtzvvvFNS6Xl55ZVXJKlCd9lVhz59+iggIEBLly51+ZXkJ598om+++abW+iq5Vuitt94qtayi709VOX78uDZs2GA/z83N1bvvvqvQ0FAFBQXZ456enho2bJjWrVunpKQk9ezZU1dffXW19IQ/H75ZGvVaQECAZs+erVmzZummm27SPffco4YNG2rnzp1677331L9/f919993V2sP48eP15ptvatSoUUpPT1dISIjef/99ffHFF0pMTFSTJk2qdf8XExoaqmHDhmnJkiXKycnRddddp9TU1FLfl1SWFStWaMmSJbr33nvVoUMHnTp1Sm+//bYcDocdHHx9fdWtWzetXbtWnTp1UtOmTdWj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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#Those are reference figures obtained from Numpy\n", "print(\"Reference timing from numpy: Uniform\")\n", "%timeit numpy.random.random(shape)\n", "print(\"Reference timing from numpy: Normal\")\n", "%timeit numpy.random.normal(0, 1, size=shape)\n", "fig, ax = subplots()\n", "ax.hist(numpy.random.random(shape).ravel())\n", "ax.set_title(\"Uniform distribution from Numpy\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With figures like 110ms for 4 Mpix of random values, it makes clear that 10 fps is a hard limit for those frame generation: Almost all the time is spent in generating those random numbers ! \n", "\n", "Let's see how the `rand` function from the standard C library behaves. \n", "\n", "## Cython implementation of the `C-rand`\n", "\n", "The C-standard library pseudo-random number generator is not of the greatest quality, but this is probably not of great importance in this case. We have a performance issue ! Let's look at some simple and direct call to `rand` via Cython. Nothing fancy, but the benchmarks !\n", "\n", "Thanks Jupyter for letting use code and profile cython code interactively." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "%load_ext Cython" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "%%cython\n", "# cython: boundscheck=False\n", "# cython: cdivision=True\n", "# cython: wraparound=False\n", "import time\n", "import numpy\n", "from libc.stdint cimport uint64_t\n", "from libc.stdlib cimport rand, RAND_MAX, srand\n", "\n", "srand( (time.time_ns()%RAND_MAX))\n", "\n", "def cython_uniform_rand(shape):\n", " cdef uint64_t size = numpy.prod(shape), idx\n", " cdef double[::1] ary = numpy.empty(size)\n", " with nogil:\n", " for idx in range(size):\n", " ary[idx] = rand()/(RAND_MAX+1.0)\n", " return numpy.asarray(ary).reshape(shape)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Using the C-rand function from Cython\n", "83.6 ms ± 1.94 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkIAAAGzCAYAAADDgXghAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQAAMt1JREFUeJzt3X1UVWXe//EPoOcA4gGfAE0U01JJ0xUm0pRmckuJlakrTTM0zcnQErLUdMAeNWsKS83Ku7DSwXSlU2I63pi6SkYLZW61tCdLGwN0EvARBPbvj/mxb4+gPCQweL1fa5215Nrfc+3vuTjKx3323nhYlmUJAADAQJ713QAAAEB9IQgBAABjEYQAAICxCEIAAMBYBCEAAGAsghAAADAWQQgAABiLIAQAAIxFEAIAAMYiCAG/09ixYxUaGuo2dvLkSU2YMEHBwcHy8PDQ1KlT66W338vDw0Nz5syxv05JSZGHh4d++umnWt/3hev6008/ycPDQy+//HKt71uS5syZIw8PjzrZ14WulPdPQ7NlyxZ5eHhoy5Yt9d0K6hBBCEYo+6F27NixCrd369ZNt95662Xb3wsvvKCUlBRNmjRJ77//vsaMGXPZ5m5oTp8+rTlz5vxH/nD5T+2tIb5/zp49q1dffVURERHy9/eXt7e3rr32Wk2ePFnffvttfbcHXFSj+m4AaOjefvttlZaWuo1t3rxZffr0UVJSUj11VTvGjBmjkSNHyul0Vvk5p0+f1tNPPy1J1QqbFa3r5Xap3mbPnq0ZM2bU6v4vpqG9f44dO6bbb79dmZmZGjx4sEaNGiU/Pz8dOHBAqampeuutt1RUVFTfbQIVIggBv1Pjxo3LjeXm5iosLOyy7aO4uFilpaVyOByXbc6a8PLykpeXV63u49SpU2rSpEmF61qXGjVqpEaN6uefyKq+f86ePSuHwyFPz/o9uD927Fjt3r1bq1ev1rBhw9y2Pfvss5o1a1aN5j19+rR8fX0vR4vARfHRGFCBsnMFPvzwQz3//PNq27atvL29NWDAAH3//fduteefy1L2vIMHDyotLU0eHh5u59Tk5uZq/PjxCgoKkre3t3r06KFly5a5zXf+uTDJycnq2LGjnE6nvv76a/sjvm+//Vb333+//P391apVK/3pT3+SZVk6fPiw7r77brlcLgUHB+vPf/5zlV5vYWGh4uPj1apVKzVt2lR33XWXfvnll3J1FZ0j9NVXXyk6OlotW7aUj4+POnTooAcffNB+La1atZIkPf300/Z6lJ13NHbsWPn5+emHH37QoEGD1LRpU40ePbrcul7o1VdfVfv27eXj46N+/fpp7969bttvvfXWCo8+nT9nZb1VdI5QcXGxnn32Wft7EhoaqqeeekqFhYVudaGhoRo8eLA+//xz9e7dW97e3rr66qv13nvvVfh6ylzq/VO2LTU1VbNnz9ZVV10lX19fFRQUSJJWrVql8PBw+fj4qGXLlrr//vv1z3/+s9zr9/Pz06FDhzR48GD5+fnpqquu0qJFiyRJe/bs0W233aYmTZqoffv2WrFixSX7laQdO3YoLS1N48ePLxeCJMnpdFbpvK5bb71V3bp1U2Zmpvr27StfX1899dRTkqS//vWviomJUZs2beR0OtWxY0c9++yzKikpqXCOr7/+Wv3795evr6+uuuoqzZ8/v9z+fvnlFw0ZMkRNmjRRYGCg4uPjy30fYQaOCAGXMG/ePHl6emratGnKz8/X/PnzNXr0aO3YsaPC+q5du+r9999XfHy82rZtq8cff1yS1KpVK505c0a33nqrvv/+e02ePFkdOnTQqlWrNHbsWOXl5emxxx5zm+vdd9/V2bNnNXHiRDmdTjVv3tzeNmLECHXt2lXz5s1TWlqannvuOTVv3lxvvvmmbrvtNr344otavny5pk2bphtvvFF9+/a95OucMGGCPvjgA40aNUo33XSTNm/erJiYmErXJzc3VwMHDlSrVq00Y8YMBQQE6KefftJHH31kv+433nhDkyZN0j333KOhQ4dKkq6//np7juLiYkVHR+vmm2/Wyy+/XOkRgPfee08nTpxQXFyczp49qwULFui2227Tnj17FBQUVGnPZarS24UmTJigZcuWafjw4Xr88ce1Y8cOzZ07V998843WrFnjVvv9999r+PDhGj9+vGJjY/XOO+9o7NixCg8P13XXXVfh/Jd6/5SFz2effVYOh0PTpk1TYWGhHA6HUlJSNG7cON14442aO3eucnJytGDBAn3xxRfavXu3AgIC7H2UlJTojjvuUN++fTV//nwtX75ckydPVpMmTTRr1iyNHj1aQ4cO1ZIlS/TAAw8oMjJSHTp0uOiafPzxx5J0Wc5j+te//qU77rhDI0eO1P33329/P1NSUuTn56eEhAT5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SKGhofL29lZERIR27txZ3y0BAID/AFd8EFq5cqUSEhKUlJSkXbt2qUePHoqOjlZubm59twYAAOrZFR+EXnnlFT300EMaN26cwsLCtGTJEvn6+uqdd96p79YAAEA9a1TfDdSmoqIiZWZmaubMmfaYp6enoqKilJGRUa6+sLBQhYWF9tf5+fmSpIKCglrpr7TwdK3MCwBAQ1EbP2PL5rQsq9LaKzoIHTt2TCUlJQoKCnIbDwoK0v79+8vVz507V08//XS58ZCQkFrrEQAAk/kn197cJ06ckL+//yVrruggVF0zZ85UQkKC/XVpaal+++03tWjRQh4eHpd1XwUFBQoJCdHhw4flcrku69z4P6xz3WCd6wbrXHdY67pRW+tsWZZOnDihNm3aVFp7RQehli1bysvLSzk5OW7jOTk5Cg4OLlfvdDrldDrdxgICAmqzRblcLv6S1QHWuW6wznWDda47rHXdqI11ruxIUJkr+mRph8Oh8PBwpaen22OlpaVKT09XZGRkPXYGAAD+E1zRR4QkKSEhQbGxserVq5d69+6t5ORknTp1SuPGjavv1gAAQD274oPQiBEjdPToUSUmJio7O1s9e/bUhg0byp1AXdecTqeSkpLKfRSHy4t1rhusc91gnesOa103/hPW2cOqyrVlAAAAV6Ar+hwhAACASyEIAQAAYxGEAACAsQhCAADAWAQhAABgLIJQLVq0aJFCQ0Pl7e2tiIgI7dy585L1q1atUpcuXeTt7a3u3btr/fr1ddRpw1addX777bd1yy23qFmzZmrWrJmioqIq/b7g36r7fi6TmpoqDw8PDRkypHYbvEJUd53z8vIUFxen1q1by+l06tprr+Xfjiqo7jonJyerc+fO8vHxUUhIiOLj43X27Nk66rZh2rZtm+688061adNGHh4eWrt2baXP2bJli2644QY5nU516tRJKSkptd6nLNSK1NRUy+FwWO+88461b98+66GHHrICAgKsnJycCuu/+OILy8vLy5o/f7719ddfW7Nnz7YaN25s7dmzp447b1iqu86jRo2yFi1aZO3evdv65ptvrLFjx1r+/v7WL7/8UsedNyzVXecyBw8etK666irrlltuse6+++66abYBq+46FxYWWr169bIGDRpkff7559bBgwetLVu2WFlZWXXcecNS3XVevny55XQ6reXLl1sHDx60Nm7caLVu3dqKj4+v484blvXr11uzZs2yPvroI0uStWbNmkvW//jjj5avr6+VkJBgff3119brr79ueXl5WRs2bKjVPglCtaR3795WXFyc/XVJSYnVpk0ba+7cuRXW33vvvVZMTIzbWEREhPXHP/6xVvts6Kq7zhcqLi62mjZtai1btqy2Wrwi1GSdi4uLrZtuuslaunSpFRsbSxCqguqu8xtvvGFdffXVVlFRUV21eEWo7jrHxcVZt912m9tYQkKC9Yc//KFW+7ySVCUIPfnkk9Z1113nNjZixAgrOjq6FjuzLD4aqwVFRUXKzMxUVFSUPebp6amoqChlZGRU+JyMjAy3ekmKjo6+aD1qts4XOn36tM6dO6fmzZvXVpsNXk3X+ZlnnlFgYKDGjx9fF202eDVZ548//liRkZGKi4tTUFCQunXrphdeeEElJSV11XaDU5N1vummm5SZmWl/fPbjjz9q/fr1GjRoUJ30bIr6+jl4xf+Kjfpw7NgxlZSUlPs1HkFBQdq/f3+Fz8nOzq6wPjs7u9b6bOhqss4Xmj59utq0aVPuLx/+T03W+fPPP9d///d/Kysrqw46vDLUZJ1//PFHbd68WaNHj9b69ev1/fff65FHHtG5c+eUlJRUF203ODVZ51GjRunYsWO6+eabZVmWiouL9fDDD+upp56qi5aNcbGfgwUFBTpz5ox8fHxqZb8cEYKx5s2bp9TUVK1Zs0be3t713c4V48SJExozZozefvtttWzZsr7buaKVlpYqMDBQb731lsLDwzVixAjNmjVLS5Ysqe/WrihbtmzRCy+8oMWLF2vXrl366KOPlJaWpmeffba+W8NlwBGhWtCyZUt5eXkpJyfHbTwnJ0fBwcEVPic4OLha9ajZOpd5+eWXNW/ePP3P//yPrr/++tpss8Gr7jr/8MMP+umnn3TnnXfaY6WlpZKkRo0a6cCBA+rYsWPtNt0A1eT93Lp1azVu3FheXl72WNeuXZWdna2ioiI5HI5a7bkhqsk6/+lPf9KYMWM0YcIESVL37t116tQpTZw4UbNmzZKnJ8cULoeL/Rx0uVy1djRI4ohQrXA4HAoPD1d6ero9VlpaqvT0dEVGRlb4nMjISLd6Sdq0adNF61GzdZak+fPn69lnn9WGDRvUq1evumi1QavuOnfp0kV79uxRVlaW/bjrrrvUv39/ZWVlKSQkpC7bbzBq8n7+wx/+oO+//94OmpL07bffqnXr1oSgi6jJOp8+fbpc2CkLnxa/t/yyqbefg7V6KrbBUlNTLafTaaWkpFhff/21NXHiRCsgIMDKzs62LMuyxowZY82YMcOu/+KLL6xGjRpZL7/8svXNN99YSUlJXD5fBdVd53nz5lkOh8NavXq19euvv9qPEydO1NdLaBCqu84X4qqxqqnuOh86dMhq2rSpNXnyZOvAgQPWunXrrMDAQOu5556rr5fQIFR3nZOSkqymTZtaf/nLX6wff/zR+tvf/mZ17NjRuvfee+vrJTQIJ06csHbv3m3t3r3bkmS98sor1u7du62ff/7ZsizLmjFjhjVmzBi7vuzy+SeeeML65ptvrEWLFnH5fEP3+uuvW+3atbMcDofVu3dv6+9//7u9rV+/flZsbKxb/Ycffmhde+21lsPhsK677jorLS2tjjtumKqzzu3bt7cklXskJSXVfeMNTHXfz+cjCFVdddd5+/btVkREhOV0Oq2rr77aev75563i4uI67rrhqc46nzt3zpozZ47VsWNHy9vb2woJCbEeeeQR6/jx43XfeAPy2WefVfjvbdnaxsbGWv369Sv3nJ49e1oOh8O6+uqrrXfffbfW+/SwLI7rAQAAM3GOEAAAMBZBCAAAGIsgBAAAjEUQAgAAxiIIAQAAYxGEAACAsQhCAADAWAQhAABgLIIQAAAwFkEIAAAYiyAEAACM9f8AYonoDl1ictQAAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "print(\"Using the C-rand function from Cython\")\n", "%timeit cython_uniform_rand(shape)\n", "fig, ax = subplots()\n", "ax.hist(cython_uniform_rand(shape).ravel())\n", "ax.set_title(\"Uniform distribution from C-rand\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The distribution looks OK but the C-rand function is almost **twice slower** than the equivalent numpy function !\n", "This explains why rewriting the function in cython with C-rand is actually slower than the initial numpy implementation (which was 5 lines long!).\n", "\n", "\n", "## C++ bound with Cython\n", "\n", "A friend of mind advertised C++, a great programming language which standard library implements many random number generator, most of them of much better quality than the one from C. Moreover, the normal distribution is directly available which makes it a great candidate for my problem and created great hopes for faster processing.\n", "\n", "I started coding on the examples provided and discovered the PRNG-feature requires a fairly recent compiler, compatible with C++-11. This is the first cold shower, I nevertheless offered C++ a try:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "%%cython\n", "# distutils: language = c++\n", "# distutils: extra_compile_args = -std=c++11\n", "# cython: boundscheck=False\n", "# cython: cdivision=True\n", "# cython: wraparound=False\n", "\n", "import cython\n", "import numpy\n", "import time\n", "cdef extern from \"\" namespace \"std\":\n", " cdef cppclass mt19937:\n", " mt19937() nogil# we need to define this constructor to stack allocate classes in Cython\n", " mt19937(unsigned int seed) # not worrying about matching the exact int type for seed\n", " cdef cppclass mt19937_64:\n", " mt19937_64() nogil\n", " mt19937_64(unsigned long long seed)\n", " cdef cppclass uniform_real_distribution[T]:\n", " uniform_real_distribution()\n", " uniform_real_distribution(T a, T b)\n", " T operator()(mt19937 gen) nogil # ignore the possibility of using other classes for \"gen\"\n", " T operator()(mt19937_64 gen) nogil # ignore the possibility of using other classes for \"gen\"\n", " cdef cppclass normal_distribution[T]:\n", " normal_distribution() nogil\n", " normal_distribution(T a, T b) nogil\n", " T operator()(mt19937 gen) nogil # ignore the possibility of using other classes for \"gen\"\n", " T operator()(mt19937_64 gen) nogil # ignore the possibility of using other classes for \"gen\"\n", " \n", "def test():\n", " cdef:\n", " mt19937 gen = mt19937(5)\n", " uniform_real_distribution[double] dist = uniform_real_distribution[double](0.0,1.0)\n", " return dist(gen)\n", "\n", "def cython_uniform_cpp(shape):\n", " cdef: \n", " Py_ssize_t size = numpy.prod(shape), idx\n", " double[::1] ary = numpy.empty(size)\n", " mt19937 gen = mt19937(time.time_ns()&((1<<32)-1))\n", " uniform_real_distribution[double] dist = uniform_real_distribution[double](0.0, 1.0)\n", " with nogil:\n", " for idx in range(size):\n", " ary[idx] = dist(gen)\n", " return numpy.asarray(ary).reshape(shape)\n", "\n", "def cython_uniform64_cpp(shape):\n", " cdef: \n", " Py_ssize_t size = numpy.prod(shape), idx\n", " double[::1] ary = numpy.empty(size)\n", " mt19937_64 gen = mt19937_64(time.time_ns())\n", " uniform_real_distribution[double] dist = uniform_real_distribution[double](0.0,1.0)\n", " with nogil:\n", " for idx in range(size):\n", " ary[idx] = dist(gen)\n", " return numpy.asarray(ary).reshape(shape)\n", "\n", "def cython_normal_cpp(mu, sigma):\n", " shape = mu.shape\n", " assert sigma.shape == shape\n", " cdef: \n", " Py_ssize_t size = numpy.prod(shape), idx\n", " double[::1] ary = numpy.empty(size)\n", " double[::1] cmu = numpy.ascontiguousarray(mu).ravel()\n", " double[::1] csigma = numpy.ascontiguousarray(sigma).ravel()\n", " mt19937 gen = mt19937(time.time_ns()&((1<<32)-1))\n", " normal_distribution[double] dist\n", " with nogil:\n", " for idx in range(size):\n", " dist = normal_distribution[double](cmu[idx], csigma[idx])\n", " ary[idx] = dist(gen)\n", " return numpy.asarray(ary).reshape(shape)\n", "\n", "def cython_normal64_cpp(mu, sigma, seed=None):\n", " shape = mu.shape\n", " assert sigma.shape == shape\n", " cdef: \n", " Py_ssize_t size = numpy.prod(shape), idx\n", " double[::1] ary = numpy.empty(size)\n", " double[::1] cmu = numpy.ascontiguousarray(mu).ravel()\n", " double[::1] csigma = numpy.ascontiguousarray(sigma).ravel()\n", " mt19937_64 gen = mt19937_64(time.time_ns() if seed is None else seed)\n", " normal_distribution[double] dist\n", " with nogil:\n", " for idx in range(size):\n", " dist = normal_distribution[double](cmu[idx], csigma[idx])\n", " ary[idx] = dist(gen)\n", " return numpy.asarray(ary).reshape(shape)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Execution time from C++:\n", "Uniform distribution, 32 and 64 bits versions of Mersenne twisters:\n", "84.3 ms ± 3.61 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", "69.7 ms ± 492 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", "Normal distribution, 32 and 64 bits versions of Mersenne twisters:\n", "334 ms ± 4.46 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", "257 ms ± 4.49 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkIAAAGzCAYAAADDgXghAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQAANGVJREFUeJzt3X9UVXW+//EXoOeA6IH8AWgQ+GNSydIbJtJUpjLSSJZlKy3H0HC8GToJlWI6UPZDo1/Y+GvKm1jpYLZG7ySK4+Boa5K0SO6YpdVkadc5oGsEzB8gsL9/zJd9PYICJiB+no+1zlry2e+9z/t82HBe7rP3xsuyLEsAAAAG8m7pBgAAAFoKQQgAABiLIAQAAIxFEAIAAMYiCAEAAGMRhAAAgLEIQgAAwFgEIQAAYCyCEAAAMBZBCEabOHGiIiIiPMZ+/PFHTZ48WSEhIfLy8tKMGTNapLefysvLS08//bT9dVZWlry8vPTdd981+XOfO6/fffedvLy89PLLLzf5c0vS008/LS8vr2Z5rnNdKfuPSer62bj99tt1++2321/X7MNZWVn22MSJE9W+ffvmaxRNgiCEy17Nm9rRo0frXN6vXz+PX1g/1QsvvKCsrCxNnTpV77zzjiZMmHDJtt3anDx5Uk8//bS2bdvW0q3Ucrn21hr3n9OnT+u1115TdHS0AgIC5Ovrq2uvvVbTpk3TV1991dLtNdqSJUs8AgtwIW1augGgJb355puqrq72GNu6dasGDx6s9PT0FuqqaUyYMEHjxo2T0+ls8DonT57UM888I0mNCpt1zeuldqHe5s6dq9TU1CZ9/vNpbfvP0aNHdccdd6igoEB33nmnHnzwQbVv31779+9Xdna23njjDVVUVLR0m42yZMkSde7cWRMnTrxk2wwPD9epU6fUtm3bS7ZNXB44IgSjtW3btlYwKC4uVmBg4CV7jsrKysvijcTHx0e+vr5N+pHRiRMnJNU9r82pTZs28vX1bZHnbuj+c/r06SYPiw0xceJE7d69W++//74++OADPfbYY0pMTFRGRoa+/vpr/eY3v7ng+hERER4fwTbUuR8zXe68vLzk6+srHx+flm4FlxhBCFecbdu2ycvLS++9956ef/55hYaGytfXV8OHD9c333zjUXv2uSw16x04cEA5OTny8vLyOG+guLhYiYmJCg4Olq+vr/r376+VK1d6bO/sc2EyMzPVs2dPOZ1OffHFF/ZHfF999ZV+9atfKSAgQF26dNFvf/tbWZalQ4cO6e6775bL5VJISIheeeWVBr3e8vJyJScnq0uXLurQoYPuuusu/fDDD7Xq6joP4tNPP1VcXJw6d+4sPz8/de/eXQ8//LD9Wrp06SJJeuaZZ+z5qHnTqzk/4h//+IdGjhypDh06aPz48bXm9VyvvfaawsPD5efnpyFDhujzzz/3WH7uuRk1zt5mfb3VdY5QZWWlnn32Wft7EhERoaeeekrl5eUedREREbrzzjv1t7/9TYMGDZKvr6969Oiht99+u87XU+NC+0/NsuzsbM2dO1dXX3212rVrp7KyMknS2rVrFRUVJT8/P3Xu3Fm/+tWv9L//+7+1Xn/79u118OBB3XnnnWrfvr2uvvpqLV68WJK0Z88eDRs2TP7+/goPD9fq1asv2K8k7dy5Uzk5OUpMTNSYMWNqLXc6nc12XldDud1uTZo0SaGhoXI6neratavuvvtue7+OiIjQ3r17tX37dvt7cPb+tHfvXg0bNkx+fn4KDQ3Vc88916BAWtc5QjW+/fZbxcXFyd/fX926ddO8efNkWdYlesVoanw0hivWggUL5O3trSeeeEKlpaXKyMjQ+PHjtXPnzjrr+/btq3feeUfJyckKDQ3V448/Lknq0qWLTp06pdtvv13ffPONpk2bpu7du2vt2rWaOHGiSkpK9Nhjj3lsa8WKFTp9+rSmTJkip9Opjh072svGjh2rvn37asGCBcrJydFzzz2njh076ve//72GDRumF198UatWrdITTzyhm266SbfddtsFX+fkyZP17rvv6sEHH9TNN9+srVu3Kj4+vt75KS4u1ogRI9SlSxelpqYqMDBQ3333nf74xz/ar3vp0qWaOnWq7rnnHt17772SpBtuuMHeRmVlpeLi4nTLLbfo5ZdfVrt27S74nG+//baOHz+upKQknT59WgsXLtSwYcO0Z88eBQcH19tzjYb0dq7Jkydr5cqVuu+++/T4449r586dmj9/vr788kutW7fOo/abb77Rfffdp8TERCUkJOitt97SxIkTFRUVpeuuu67O7V9o/6l5k3722WflcDj0xBNPqLy8XA6HQ1lZWZo0aZJuuukmzZ8/X0VFRVq4cKE++ugj7d692+PoUlVVlX75y1/qtttuU0ZGhlatWqVp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wYABAyTJ/nis5iqwkpKSn9bo/7d+/XqPy8J37dqlnTt3enz/evbsqX379unIkSP22P/8z//oo48+8thWY3obOXKkpNrz8uqrr0pSg66yawoDBw5UUFCQli1b5vGR5KZNm/Tll182eV8xMTG64447tHz5cq1fv77W8oqKCj3xxBNN2kNjnDx5UqdPn/YY69mzpzp06OAxf/7+/nXuFyNHjtTHH3+sXbt22WNHjhzRqlWrflJfixYtsv9tWZYWLVqktm3bavjw4T9pu2geHBHCZS8oKEhpaWmaO3eubrvtNt11111q166dduzYoT/84Q8aMWKERo0a1aQ9TJkyRb///e81ceJEFRQUKCIiQu+//74++ugjZWZmNvhk00ttwIABeuCBB7RkyRKVlpbq5ptvVl5eXq37JdVl5cqVWrJkie655x717NlTx48f15tvvimXy2UHBz8/P0VGRmrNmjW69tpr1bFjR/Xr10/9+vW7qH579eqlW265RVOnTlV5ebkyMzPVqVMnzZw50655+OGH9eqrryouLk6JiYkqLi7WsmXLdN1119kn9za2t/79+yshIUFvvPGGSkpKNGTIEO3atUsrV67U6NGjNXTo0It6PT9V27Zt9eKLL2rSpEkaMmSIHnjgAfvy+YiICCUnJzd5D2+//bZGjBihe++9V6NGjdLw4cPl7++vr7/+WtnZ2frnP/952dxL6KuvvtLw4cN1//33KzIyUm3atNG6detUVFSkcePG2XVRUVFaunSpnnvuOfXq1UtBQUEaNmyYZs6cqXfeeUd33HGHHnvsMfvy+ZojvhfD19dXubm5SkhIUHR0tDZt2qScnBw99dRT571gApcXghBahTlz5igiIkKLFi3SvHnzVFlZqe7du+uZZ57RrFmzLvl5Kufy8/PTtm3blJqaqpUrV6qsrEy9e/fWihUrLult/C/GW2+9pS5dumjVqlVav369hg0bppycnFofI56rJgxkZ2erqKhIAQEBGjRokFatWuVxUu/y5cs1ffp0JScnq6KiQunp6RcdhB566CF5e3srMzNTxcXFGjRokBYtWuTxcUvfvn319ttvKy0tTSkpKYqMjNQ777yj1atX1/q7Yo3pbfny5erRo4eysrK0bt06hYSEaPbs2S3+pzAmTpyodu3aacGCBZo1a5b8/f11zz336MUXX7ykdzg/ny5dumjHjh1asmSJ1qxZozlz5qiiokLh4eG66667at0jqyWFhYXpgQceUF5ent555x21adNGffr00XvvvedxQ8i0tDR9//33ysjI0PHjxzVkyBANGzZMXbt21V//+ldNnz5dCxYsUKdOnfTII4+oW7duHldtNoaPj49yc3M1depUPfnkk+rQoYPS09OVlpZ2qV42mpiX1RxnjgIAAFyGOEcIAAAYiyAEAACMRRACAADGIggBAABjEYQAAICxCEIAAMBY3EfoAqqrq3X48GF16NDhkv+pAQAA0DQsy9Lx48fVrVu3eu8zRxC6gMOHD9d7UzoAAHB5OnTokEJDQy9YQxC6gJo/m3Do0CG5XK4W7gYAADREWVmZwsLCGvTnjwhCF1DzcZjL5SIIAQDQyjTktBZOlgYAAMYiCAEAAGMRhAAAgLEIQgAAwFgEIQAAYCyCEAAAMBZBCAAAGIsgBAAAjEUQAgAAxiIIAQAAYxGEAACAsQhCAADAWAQhAABgLIIQAAAwVpuWbgCtS0RqTku30GjfLYhv6RYAAJcpglALao2hojVqjfPcGsNba5zn1qg17hutFft082jpfZogBACtCG/OwKVFEAIuQ7zZAUDz4GRpAABgLIIQAAAwFkEIAAAYiyAEAACMRRACAADGIggBAABjEYQAAICxCEIAAMBYBCEAAGAsghAAADAWQQgAABiLIAQAAIxFEAIAAMYiCAEAAGMRhAAAgLEIQgAAwFgEIQAAYCyCEAAAMBZBCAAAGIsgBAAAjEUQAgAAxiIIAQAAYxGEAACAsQhCAADAWAQhAABgLIIQAAAwFkEIAAAYiyAEAACMRRACAADGIggBAABjEYQAAICxCEIAAMBYPykILViwQF5eXpoxY4Y9dvr0aSUlJalTp05q3769xowZo6KiIo/1Dh48qPj4eLVr105BQUF68sknVVlZ6VGzbds23XjjjXI6nerVq5eysrJqPf/ixYsVEREhX19fRUdHa9euXR7LG9ILAAAw10UHoU8++US///3vdcMNN3iMJycn64MPPtDatWu1fft2HT58WPfee6+9vKqqSvHx8aqoqNCOHTu0cuVKZWVlKS0tza45cOCA4uPjNXToUBUWFmrGjBmaPHmyNm/ebNesWbNGKSkpSk9P12effab+/fsrLi5OxcXFDe4FAACYzcuyLKuxK/3444+68cYbtWTJEj33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9Gm7Zvz48dq7d6+2bNmiDRs26MMPP9SUKVPs5WVlZRoxYoTCw8NVUFCgl156SU8//bTeeOMNu2bHjh164IEHlJiYqN27d2v06NEaPXq0Pv/880b1AgAAzOVlWZZ10St7eWndunUaPXq0pH8fgenWrZsef/xxPfHEE5Kk0tJSBQcHKysrS+PGjdOXX36pyMhIffLJJxo4cKAkKTc3VyNHjtQPP/ygbt26aenSpZozZ47cbrccDockKTU1VevXr9e+ffskSWPHjtWJEye0YcMGu5/BgwdrwIABWrZsWYN6qU9ZWZkCAgJUWloql8t1sdN0XhGpOZd8mwAAtCbfLYi/5NtszPv3JT1H6MCBA3K73YqNjbXHAgICFB0drfz8fElSfn6+AgMD7RAkSbGxsfL29tbOnTvtmttuu80OQZIUFxen/fv369ixY3bN2c9TU1PzPA3p5Vzl5eUqKyvzeAAAgCvXJQ1CbrdbkhQcHOwxHhwcbC9zu90KCgryWN6mTRt17NjRo6aubZz9HOerOXt5fb2ca/78+QoICLAfYWFhDXjVAACgteKqsbPMnj1bpaWl9uPQoUMt3RIAAGhClzQIhYSESJKKioo8xouKiuxlISEhKi4u9lheWVmpf/3rXx41dW3j7Oc4X83Zy+vr5VxOp1Mul8vjAQAArlyXNAh1795dISEhysvLs8fKysq0c+dOxcTESJJiYmJUUlKigoICu2br1q2qrq5WdHS0XfPhhx/qzJkzds2WLVvUu3dvXXXVVXbN2c9TU1PzPA3pBQAAmK3RQejHH39UYWGhCgsLJf37pOTCwkIdPHhQXl5emjFjhp577jn96U9/0p49e/TQQw+pW7du9pVlffv21R133KFf//rX2rVrlz766CNNmzZN48aNU7du3SRJDz74oBwOhxITE7V3716tWbNGCxcuVEpKit3HY489ptzcXL3yyivat2+fnn76aX366aeaNm2aJDWoFwAAYLY2jV3h008/1dChQ+2va8JJQkKCsrKyNHPmTJ04cUJTpkxRSUmJbrnlFuXm5srX19deZ9WqVZo2bZqGDx8ub29vjRkzRq+//rq9PCAgQH/+85+VlJSkqKgode7cWWlpaR73Grr55pu1evVqzZ07V0899ZR+9rOfaf369erXr59d05BeAACAuX7SfYSudNxHCACApnVF3UcIAACgNSEIAQAAYxGEAACAsQhCAADAWAQhAABgLIIQAAAwFkEIAAAYiyAEAACMRRACAADGIggBAABjEYQAAICxCEIAAMBYBCEAAGAsghAAADAWQQgAABiLIAQAAIxFEAIAAMYiCAEAAGMRhAAAgLEIQgAAwFgEIQAAYCyCEAAAMBZBCAAAGIsgBAAAjEUQAgAAxiIIAQAAYxGEAACAsQhCAADAWAQhAABgLIIQAAAwFkEIAAAYiyAEAACMRRACAADGIggBAABjEYQAAICxCEIAAMBYBCEAAGAsghAAADAWQQgAABiLIAQAAIxFEAIAAMYiCAEAAGMRhAAAgLEIQgAAwFgEIQAAYCyCEAAAMBZBCAAAGIsgBAAAjEUQAgAAxiIIAQAAYxGEAACAsQhCAADAWAQhAABgLIIQAAAwFkEIAAAYiyAEAACMRRACAADGIggBAABjEYQAAICxCEIAAMBYBCEAAGAsghAAADAWQQgAABiLIAQAAIxFEAIAAMYiCAEAAGMRhAAAgLGMCEKLFy9WRESEfH19FR0drV27drV0SwAA4DJwxQehNWvWKCUlRenp6frss8/Uv39/xcXFqbi4uKVbAwAALeyKD0Kvvvqqfv3rX2vSpEmKjIzUsmXL1K5dO7311lst3RoAAGhhbVq6gaZUUVGhgoICzZ492x7z9vZWbGys8vPza9WXl5ervLzc/rq0tFSSVFZW1iT9VZefbJLtAgDQWjTFe2zNNi3Lqrf2ig5CR48eVVVVlYKDgz3Gg4ODtW/fvlr18+fP1zPPPFNrPCwsrMl6BADAZAGZTbft48ePKyAg4II1V3QQaqzZs2crJSXF/rq6ulr/+te/1KlTJ3l5eV3S5yorK1NYWJgOHTokl8t1SbeN/8M8Nw/muXkwz82HuW4eTTXPlmXp+PHj6tatW721V3QQ6ty5s3x8fFRUVOQxXlRUpJCQkFr1TqdTTqfTYywwMLApW5TL5eKHrBkwz82DeW4ezHPzYa6bR1PMc31Hgmpc0SdLOxwORUVFKS8vzx6rrq5WXl6eYmJiWrAzAABwObiijwhJUkpKihISEjRw4EANGjRImZmZOnHihCZNmtTSrQEAgBZ2xQehsWPH6siRI0pLS5Pb7daAAQOUm5tb6wTq5uZ0OpWenl7rozhcWsxz82Cemwfz3HyY6+ZxOcyzl9WQa8sAAACuQFf0OUIAAAAXQhACAADGIggBAABjEYQAAICxCEIAAMBYBKEmtHjxYkVERMjX11fR0dHatWvXBevXrl2rPn36yNfXV9dff702btzYTJ22bo2Z5zfffFO33nqrrrrqKl111VWKjY2t9/uCf2vs/lwjOztbXl5eGj16dNM2eIVo7DyXlJQoKSlJXbt2ldPp1LXXXsvvjgZo7DxnZmaqd+/e8vPzU1hYmJKTk3X69Olm6rZ1+vDDDzVq1Ch169ZNXl5eWr9+fb3rbNu2TTfeeKOcTqd69eqlrKysJu9TFppEdna25XA4rLfeesvau3ev9etf/9oKDAy0ioqK6qz/6KOPLB8fHysjI8P64osvrLlz51pt27a19uzZ08ydty6NnecHH3zQWrx4sbV7927ryy+/tCZOnGgFBARYP/zwQzN33ro0dp5rHDhwwLr66qutW2+91br77rubp9lWrLHzXF5ebg0cONAaOXKk9be//c06cOCAtW3bNquwsLCZO29dGjvPq1atspxOp7Vq1SrrwIED1ubNm62uXbtaycnJzdx567Jx40Zrzpw51h//+EdLkrVu3boL1n/77bdWu3btrJSUFOuLL76wfve731k+Pj5Wbm5uk/ZJEGoigwYNspKSkuyvq6qqrG7dulnz58+vs/7++++34uPjPcaio6Ot//zP/2zSPlu7xs7zuSorK60OHTpYK1eubKoWrwgXM8+VlZXWzTffbC1fvtxKSEggCDVAY+d56dKlVo8ePayKiormavGK0Nh5TkpKsoYNG+YxlpKSYv385z9v0j6vJA0JQjNnzrSuu+46j7GxY8dacXFxTdiZZfHRWBOoqKhQQUGBYmNj7TFvb2/FxsYqPz+/znXy8/M96iUpLi7uvPW4uHk+18mTJ3XmzBl17Nixqdps9S52nufNm6egoCAlJiY2R5ut3sXM85/+9CfFxMQoKSlJwcHB6tevn1544QVVVVU1V9utzsXM880336yCggL747Nvv/1WGzdu1MiRI5ulZ1O01PvgFf8nNlrC0aNHVVVVVevPeAQHB2vfvn11ruN2u+usd7vdTdZna3cx83yuWbNmqVu3brV++PB/Lmae//a3v+m//uu/VFhY2AwdXhkuZp6//fZbbd26VePHj9fGjRv1zTff6NFHH9WZM2eUnp7eHG23Ohczzw8++KCOHj2qW265RZZlqbKyUo888oieeuqp5mjZGOd7HywrK9OpU6fk5+fXJM/LESEYa8GCBcrOzta6devk6+vb0u1cMY4fP64JEybozTffVOfOnVu6nStadXW1goKC9MYbbygqKkpjx47VnDlztGzZspZu7Yqybds2vfDCC1qyZIk+++wz/fGPf1ROTo6effbZlm4NlwBHhJpA586d5ePjo6KiIo/xoqIihYSE1LlOSEhIo+pxcfNc4+WXX9aCBQv0l7/8RTfccENTttnqNXae//GPf+i7777TqFGj7LHq6mpJUps2bbR//3717NmzaZtuhS5mf+7atavatm0rHx8fe6xv375yu92qqKiQw+Fo0p5bo4uZ59/+9reaMGGCJk+eLEm6/vrrdeLECU2ZMkVz5syRtzfHFC6F870PulyuJjsaJHFEqEk4HA5FRUUpLy/PHquurlZeXp5iYmLqXCcmJsajXpK2bNly3npc3DxLUkZGhp599lnl5uZq4MCBzdFqq9bYee7Tp4/27NmjwsJC+3HXXXdp6NChKiwsVFhYWHO232pczP7885//XN98840dNCXpq6++UteuXQlB53Ex83zy5MlaYacmfFr83fJLpsXeB5v0VGyDZWdnW06n08rKyrK++OILa8qUKVZgYKDldrsty7KsCRMmWKmpqXb9Rx99ZLVp08Z6+eWXrS+//NJKT0/n8vkGaOw8L1iwwHI4HNb7779v/fOf/7Qfx48fb6mX0Co0dp7PxVVjDdPYeT548KDVoUMHa9q0adb+/futDRs2WEFBQdZzzz3XUi+hVWjsPKenp1sdOnSw/vCHP1jffvut9ec//9nq2bOndf/997fUS2gVjh8/bu3evdvavXu3Jcl69dVXrd27d1vff/+9ZVmWlZqaak2YMMGur7l8/sknn7S+/PJLa/HixVw+39r97ne/s6655hrL4XBYgwYNsj7++GN72ZAhQ6yEhASP+vfee8+69tprLYfDYV133XVWTk5OM3fcOjVmnsPDwy1JtR7p6enN33gr09j9+WwEoYZr7Dzv2LHDio6OtpxOp9WjRw/r+eeftyorK5u569anMfN85swZ6+mnn7Z69uxp+fr6WmFhYdajjz5qHTt2rPkbb0X++te/1vn7tmZuExISrCFDhtRaZ8CAAZbD4bB69OhhrVixosn79LIsjusBAAAzcY4QAAAwFkEIAAAYiyAEAACMRRACAADGIggBAABjEYQAAICxCEIAAMBYBCEAAGAsghAAADAWQQgAABiLIAQAAIz1/wDIFWbCJTgGGgAAAABJRU5ErkJggg==", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "print(\"Execution time from C++:\")\n", "print(\"Uniform distribution, 32 and 64 bits versions of Mersenne twisters:\")\n", "%timeit cython_uniform_cpp(shape)\n", "%timeit cython_uniform64_cpp(shape)\n", "print(\"Normal distribution, 32 and 64 bits versions of Mersenne twisters:\")\n", "a=numpy.ones(shape)\n", "%timeit cython_normal_cpp(a,a)\n", "%timeit cython_normal64_cpp(a,a)\n", "fig, ax = subplots()\n", "ax.hist(cython_uniform64_cpp(shape).ravel())\n", "ax.set_title(\"Uniform distribution from C++ stdlib\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is really disappointing! While C++ offers ready to use pseudo random number gernertors, which looks OK, but their performances are at least twice slower than what numpy offers !\n", "\n", "Note: the 32-bits version is even slower than the 64 bits version (running on a 64-bits computer)\n", "\n", "Sorry C++ is not the proper tool.\n", "\n", "## Numpy's C-API\n", "\n", "Since *numpy* was found to be the fastest, I descided to use the tools available within it. \n", "\n", "I do not especially like the idea of direct binding to the numpy ABI because it is likely to make the binaries much more difficult to distribute via `pip` and end-users are likely to experience random segmentation faults if they use a numpy version which is different from the one used for packaging.\n", "\n", "I found this code pretty complicated with those *PyCapsules*, but it is just a copy/paste from what was found on the numpy documentation at https://numpy.org/doc/stable/reference/random/extending.html\n", "\n", "The PCG generator is apparently better in quality than the Mersenne-Twister used with the C++ test." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "%%cython\n", "# cython: language_level=3\n", "# cython: boundscheck=False\n", "# cython: cdivision=True\n", "# cython: wraparound=False\n", "\n", "\"\"\"\n", "This file shows how the to use a BitGenerator to create a distribution.\n", "\"\"\"\n", "import numpy\n", "from cpython.pycapsule cimport PyCapsule_IsValid, PyCapsule_GetPointer\n", "from libc.stdint cimport uint16_t, uint64_t\n", "from numpy.random cimport bitgen_t\n", "# from numpy.random import PCG64\n", "from numpy.random import MT19937\n", "from numpy.random.c_distributions cimport (\n", " random_standard_uniform_fill, random_standard_uniform_fill_f)\n", "\n", "def cython_uniform_cnp(shape):\n", " \"\"\"\n", " Create an array of `n` uniformly distributed doubles.\n", " A 'real' distribution would want to process the values into\n", " some non-uniform distribution\n", " \"\"\"\n", " cdef:\n", " Py_ssize_t i, n=numpy.prod(shape)\n", " bitgen_t *rng\n", " const char *capsule_name = \"BitGenerator\"\n", " double[::1] random_values\n", "\n", " # x = PCG64()\n", " x = MT19937()\n", " capsule = x.capsule\n", " # Optional check that the capsule if from a BitGenerator\n", " if not PyCapsule_IsValid(capsule, capsule_name):\n", " raise ValueError(\"Invalid pointer to anon_func_state\")\n", " # Cast the pointer\n", " rng = PyCapsule_GetPointer(capsule, capsule_name)\n", " random_values = numpy.empty(n, dtype='float64')\n", " with x.lock, nogil:\n", " for i in range(n):\n", " # Call the function\n", " random_values[i] = rng.next_double(rng.state)\n", " randoms = numpy.asarray(random_values)\n", "\n", " return randoms.reshape(shape)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Performance obtained from the C-API of Numpy\n", "36.3 ms ± 289 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkIAAAGzCAYAAADDgXghAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQAANl9JREFUeJzt3XtUVXX+//EXoAfwcvAKeCHxkqlpMmIS3UhjZBIrS0c0U7zlN0MbJfMyOV66aZaF5a3LNzHTNJtoSgxzMHWV5IXk+1NTy1FHG+cArhLMCwjs3x+z2OMRFDCB8PN8rHXW6nz2e+/zPp+zj+fVPnsfPCzLsgQAAGAgz+puAAAAoLoQhAAAgLEIQgAAwFgEIQAAYCyCEAAAMBZBCAAAGIsgBAAAjEUQAgAAxiIIAQAAYxGEUKMNHz5cwcHBbmO//PKLRo8ercDAQHl4eGjChAnV0tuv5eHhoVmzZtn3ExMT5eHhoaNHj1b6Y186r0ePHpWHh4deeeWVSn9sSZo1a5Y8PDyq5LEudb3sPwDKhyCESlf8oXby5MlSl3fu3Fn33HPPNXu8F198UYmJiRo7dqxWrFihoUOHXrNt1zRnz57VrFmztHnz5upupYTfam81bf/x8PCQh4eH5s+fX2JZcXjetWtXNXR27RU/n2spKSlJ9913n5o0aSKHw6HmzZtr4MCB2rRpU4W206NHD3l4eGjJkiWlLi/uvfjm4+Oj9u3ba9y4ccrMzLTrNm/eLA8PD3300Ue/6nmh/GpVdwPAr/H222+rqKjIbWzTpk267bbbNHPmzGrqqnIMHTpUgwYNkre3d7nXOXv2rGbPni1JFQqbpc3rtXal3qZPn66pU6dW6uNfTk3df15++WWNHTtWderUqe5WagTLsjRy5EglJibqd7/7neLj4xUYGKh///vfSkpK0r333quvv/5at99+e5nb+uGHH7Rz504FBwdr5cqVGjt27GVrn332WbVu3Vrnz5/XV199pSVLlmj9+vXau3cvr101IQihRqtdu3aJsaysLHXq1OmaPUZBQYGKiorkcDiu2TavhpeXl7y8vCr1Mc6cOaO6deuWOq9VqVatWqpVq3r+eSrv/nP+/Hk5HA55elb/gfWQkBBlZGRo6dKlio+Pr+52aoT58+crMTFREyZM0Kuvvup2pOmZZ57RihUryr0Pvv/++/L399f8+fM1YMAAHT16tMRX9sXuu+8+de/eXZI0evRoNW7cWK+++qr+9re/afDgwb/6eaHiqv8dDFyi+NDwhx9+qBdeeEEtW7aUj4+P7r33Xh06dMit9uJzWYrXO3LkiJKTk+1D0MXn1GRlZWnUqFEKCAiQj4+PunbtquXLl7tt7+JzYRISEtS2bVt5e3vru+++s7/i+/777/Xoo4/Kz89PTZs21V/+8hdZlqXjx4/rwQcflNPpVGBgYKlfVZQmLy9PEydOVNOmTVW/fn098MAD+vHHH0vUlXaO0K5duxQVFaUmTZrI19dXrVu31siRI+3n0rRpU0nS7Nmz7fkoPu9o+PDhqlevnv7xj3+oT58+ql+/voYMGVJiXi/12muvqVWrVvL19VVERIT27t3rtvyee+4p9ejTxdssq7fSzhEqKCjQc889Z78mwcHB+vOf/6y8vDy3uuDgYPXt21dfffWVevToIR8fH7Vp00bvvfdeqc+n2JX2n+Jlq1ev1vTp09WiRQvVqVNHubm5kqS1a9cqNDRUvr6+atKkiR599FH961//KvH869Wrp2PHjqlv376qV6+eWrRooUWLFkmS9uzZo169eqlu3bpq1aqVVq1adcV+L3bHHXeoV69emjdvns6dO3fF2vK8PpL7e2HRokVq06aN6tSpo969e+v48eOyLEvPPfecWrZsKV9fXz344IP66aef3LZZ/Fp88cUXCgkJkY+Pjzp16qSPP/7Yrjl8+LA8PDz02muvlehp27Zt8vDw0AcffHDZ53Ol98DlnDt3TnPmzFGHDh30yiuvlPp129ChQ9WjR48rbqfYqlWrNGDAAPXt21d+fn4Veu169eolSTpy5Ei518G1RRDCb9bcuXOVlJSkSZMmadq0afrmm2/sD+rSdOzYUStWrFCTJk0UEhKiFStWaMWKFWratKnOnTune+65RytWrNCQIUP08ssvy8/PT8OHD9eCBQtKbGvZsmV64403NGbMGM2fP1+NGjWyl8XExKioqEhz585VWFiYnn/+eSUkJOj3v/+9WrRooZdeeknt2rXTpEmTtHXr1jKf5+jRo5WQkKDevXtr7ty5ql27tqKjo8tcLysrS71799bRo0c1depUvfHGGxoyZIi++eYbSVLTpk3t8xUeeughez4efvhhexsFBQWKioqSv7+/XnnlFfXv3/+Kj/nee+/p9ddfV1xcnKZN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GDFCERERGjx4sDIzM7VgwQIFBwdr4sSJ1dJXRESEIiIiSv1qpryvz7XSvn17jRo1Sjt37lRAQIDeffddZWZmatmyZSVqhw0bptdff11ffvmlXnrppTK3XZ73wOU8/fTT2rdvn+bPn68vv/xSAwYMUGBgoFwulz755BPt2LHD/gqsNCtXrlTjxo0v+8vTDzzwgN5++20lJydf8WchUP04IoQq8cwzz+j9999XYWGhnn32WU2aNEm7d+/W7Nmz9emnn1b69+S+vr7avHmzhgwZouXLl+upp57STz/9pGXLlulPf/pTpT52Wd599109+eSTSklJ0eTJk3XhwoVSrza6VEREhLp3767Vq1frySef1Lx583TjjTdq06ZNbif1vvPOO2rRooUmTpyowYMH/6q/YTRs2DCNHz9eCxcu1AsvvKCbb75ZmzZtUrNmzeyajh076r333lNOTo7i4+P16aefasWKFerWrVuJ7VWkt3feeUezZ8/Wzp07NWHCBG3atEnTpk274lcgVWH48OFas2aN8vPzNWXKFL355pt66KGH9NVXX9lXNVWHi/9g78Uq8vpcC8XnzKxfv15Tp07VhQsXtGbNmlJPEA4NDdXNN98sT0/PK/5mWLHyvgdK4+npqffee08fffSRmjRpoldeeUVjxozRG2+8odatW2vz5s0KDw8vdd2srCz9/e9/V58+fS57rtS9996rOnXq6P333y/zeaB6eVhVceYpAMA4wcHB6ty5s9atW1fudX73u9+pUaNGSk1NrcTOgP/iiBAA4Ddh165dysjI0LBhw6q7FRiEc4QAANVq7969Sk9P1/z589WsWTPFxMRUd0swCEeEAADV6qOPPtKIESN04cIFffDBB6X+rhhQWThHCAAAGIsjQgAAwFgEIQAAYCxOlr6CoqIinThxQvXr17/mf6oAAABUDsuydPr0aTVv3rzM36kjCF3BiRMnSvyFZQAAUDMcP35cLVu2vGINQegKiv/swvHjx+V0Oqu5GwAAUB65ubkKCgoq159PIghdQfHXYU6nkyAEAEANU57TWjhZGgAAGIsgBAAAjEUQAgAAxiIIAQAAYxGEAACAsQhCAADAWAQhAABgLIIQAAAwFkEIAAAYiyAEAACMRRACAADGIggBAABjEYQAAICxCEIAAMBYtaq7AaCyBU9Nru4WKuzo3OjqbgG/UTVxf5Zq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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "print(\"Performance obtained from the C-API of Numpy\")\n", "%timeit cython_uniform_cnp(shape)\n", "fig, ax = subplots()\n", "ax.hist(cython_uniform_cnp(shape).ravel())\n", "ax.set_title(\"Uniform distribution from Numpy's C-API\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally ! this solution gives faster random number than both the C and C++ version, and even slightly faster than the numpy version. But this is normal: I removed most of the flexibility offered by numpy and tailored it to my needs.\n", "\n", "## Cython written Mersenne-twisters\n", "\n", "As an alternative, I found the same algorithm we tested in C++, already implemented in Cython:\n", "https://github.com/ananswam/cython_random\n", "\n", "This again the Mersenne-twister. Let's see how it behaves:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "%%cython\n", "# cython: boundscheck=False\n", "# cython: cdivision=True\n", "# cython: wraparound=False\n", "import numpy\n", "cimport numpy as np\n", "import time\n", "\n", "# MT Stuff\n", "cdef unsigned NN = 312\n", "cdef unsigned MM = 156\n", "cdef unsigned long long MATRIX_A = 0xB5026F5AA96619E9ULL\n", "cdef unsigned long long UM = 0xFFFFFFFF80000000ULL\n", "cdef unsigned long long LM = 0x7FFFFFFFULL\n", "cdef unsigned long long mt[312]\n", "cdef unsigned mti = NN + 1\n", "cdef unsigned long long mag01[2]\n", "\n", "cdef mt_seed(unsigned long long seed):\n", " global mt\n", " global mti\n", " global mag01\n", " global NN\n", " global MATRIX_A\n", " mt[0] = seed\n", " for mti in range(1,NN):\n", " mt[mti] = (6364136223846793005ULL * (mt[mti-1] ^ (mt[mti-1] >> 62)) + mti)\n", "\n", " mag01[0] = 0ULL\n", " mag01[1] = MATRIX_A\n", " mti = NN\n", "\n", "\n", "cdef unsigned long long genrand64() nogil:\n", " cdef int i\n", " cdef unsigned long long x\n", " global mag01\n", " global mti\n", " global mt\n", " global NN\n", " global MM\n", " global UM\n", " global LM\n", "\n", " if mti >= NN:\n", " for i in range(NN-MM):\n", " x = (mt[i]&UM) | (mt[i+1]&LM)\n", " mt[i] = mt[i+MM] ^ (x>>1) ^ mag01[int(x&1ULL)]\n", "\n", " for i in range(NN-MM, NN-1):\n", " x = (mt[i]&UM)|(mt[i+1]&LM)\n", " mt[i] = mt[i+(MM-NN)] ^ (x>>1) ^ mag01[int(x&1ULL)]\n", "\n", " x = (mt[NN-1]&UM)|(mt[0]&LM)\n", " mt[NN-1] = mt[MM-1] ^ (x>>1) ^ mag01[int(x&1ULL)]\n", " mti = 0\n", "\n", " x = mt[mti]\n", " mti += 1\n", " x ^= (x >> 29) & 0x5555555555555555ULL\n", " x ^= (x << 17) & 0x71D67FFFEDA60000ULL\n", " x ^= (x << 37) & 0xFFF7EEE000000000ULL\n", " x ^= (x >> 43);\n", "\n", " return x\n", "\n", "def py_rand_int():\n", " return genrand64()\n", "\n", "# Functions\n", "\n", "# Seed the random number generator\n", "cdef seed_random(seed=None):\n", " \"\"\"\n", " Seed the C random number generator with the current system time.\n", " :return: none\n", " \"\"\"\n", " if seed is None:\n", " mt_seed(time.time_ns())\n", " else:\n", " mt_seed(seed)\n", "\n", "def py_seed_random(unsigned long long seed = 0):\n", " seed_random(seed)\n", "\n", "cdef double uniform_rv() nogil:\n", " \"\"\"\n", " Generate a uniform random variable in [0,1]\n", " :return: (double) a random uniform number in [0,1]\n", " \"\"\"\n", " return (genrand64() >> 11) * (1.0/9007199254740991.0)\n", "\n", "def cython_uniform_mt(shape):\n", " cdef Py_ssize_t size = numpy.prod(shape), idx\n", " cdef double[::1] ary = numpy.empty(size)\n", " mt_seed(time.time_ns())\n", " with nogil:\n", " for idx in range(size):\n", " ary[idx] = uniform_rv()\n", " return numpy.asarray(ary).reshape(shape)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Performances of MT64 implemented in Cython\n", "36.1 ms ± 496 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkIAAAGzCAYAAADDgXghAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQAAMppJREFUeJzt3XtUVPXex/EPoDOgOJCpoEliWiqZekIlumnFkZIulq40TdE0nww9CVlqGthV83TB8lb5JFYYZid9SsyOR9NWydFCeR61tJulZYCuhDEvILCfP85iH0dQGBI48Hu/1pq15Le/89vf+c0gH2b23vhYlmUJAADAQL713QAAAEB9IQgBAABjEYQAAICxCEIAAMBYBCEAAGAsghAAADAWQQgAABiLIAQAAIxFEAIAAMYiCAE1NHr0aIWHh3uM/f777xo3bpxCQ0Pl4+OjyZMn10tvf5SPj49mzZplf52WliYfHx/9+OOPtb7vM9f1xx9/lI+Pj55//vla37ckzZo1Sz4+PnWyrzM1ltdPbdi0aZN8fHz03nvv1XcraGQIQmjUyn+oHT58uNLt3bt3V//+/c/b/p599lmlpaVpwoQJeuuttzRy5MjzNndDc/z4cc2aNUubNm2q71Yq+E/trSG+fk6ePKmXXnpJUVFRCgoKkr+/vy677DJNnDhR33zzjdfzLV++XKmpqee/UeAsmtR3A0BD9frrr6usrMxjbOPGjbrqqquUkpJST13VjpEjR2rYsGFyOp3Vvs/x48f1xBNPSJJXYbOydT3fztXbzJkzNW3atFrd/9k0tNfP4cOHdfPNNys7O1u33nqrhg8frsDAQO3du1cZGRl67bXXVFxc7NWcy5cv165du3g3DHWGIATUUNOmTSuM5efnKyIi4rzto6SkRGVlZXI4HOdtzprw8/OTn59fre7j2LFjat68eaXrWpeaNGmiJk3q57/G6r5+Tp48KYfDIV/f+n1Tf/To0dqxY4fee+89DR482GPbU089pRkzZtRTZ0D18dEYcJry4xDeffddPfPMM2rfvr38/f1100036bvvvvOoPf1YlvL77du3T5mZmfLx8fE4piY/P19jx45VSEiI/P391bNnTy1btsxjvtOPhUlNTVWnTp3kdDr11Vdf2R/xffPNN7r33nsVFBSk1q1b6/HHH5dlWTpw4IDuuOMOuVwuhYaG6oUXXqjW4y0qKlJiYqJat26tFi1a6Pbbb9fPP/9coa6yY4S+/PJLxcbGqlWrVgoICFDHjh1133332Y+ldevWkqQnnnjCXo/y445Gjx6twMBAff/99xo4cKBatGihESNGVFjXM7300kvq0KGDAgIC1K9fP+3atctje//+/St99+n0OavqrbJjhEpKSvTUU0/Zz0l4eLgee+wxFRUVedSFh4fr1ltv1Weffaa+ffvK399fl1xyid58881KH0+5c71+yrdlZGRo5syZuuiii9SsWTO53W5J0sqVKxUZGamAgAC1atVK9957r3755ZcKjz8wMFD79+/XrbfeqsDAQF100UVasGCBJGnnzp268cYb1bx5c3Xo0EHLly8/Z7+StHXrVmVmZmrs2LEVQpAkOZ1O+7iupUuXysfHRzt27KhQ9+yzz8rPz0+//PKL+vfvr8zMTP3000/2Gpz5WigrK6vye9Pbdfnll180aNAgBQYGqnXr1poyZYpKS0urXAM0DrwjBFRizpw58vX11ZQpU1RYWKi5c+dqxIgR2rp1a6X13bp101tvvaXExES1b99eDz/8sCSpdevWOnHihPr376/vvvtOEydOVMeOHbVy5UqNHj1aBQUFeuihhzzmWrp0qU6ePKnx48fL6XSqZcuW9rahQ4eqW7dumjNnjjIzM/X000+rZcuWevXVV3XjjTfqueeeU3p6uqZMmaI+ffro+uuvP+fjHDdunN5++20NHz5cV199tTZu3Ki4uLgq1yc/P18DBgxQ69atNW3aNAUHB+vHH3/U+++/bz/uRYsWacKECbrzzjt11113SZJ69Ohhz1FSUqLY2Fhde+21ev7559WsWbNz7vPNN9/U0aNHlZCQoJMnT2revHm68cYbtXPnToWEhFTZc7nq9HamcePGadmyZRoyZIgefvhhbd26VbNnz9bXX3+tVatWedR+9913GjJkiMaOHav4+Hi98cYbGj16tCIjI3X55ZdXOv+5Xj/l4fOpp56Sw+HQlClTVFRUJIfDobS0NI0ZM0Z9+vTR7NmzlZeXp3nz5unzzz/Xjh07FBwcbO+jtLRUt9xyi66//nrNnTtX6enpmjhxopo3b64ZM2ZoxIgRuuuuu7R48WKNGjVK0dHR6tix41nX5IMPPpCkah3HNGTIECUkJCg9PV1/+tOfPLalp6erf//+uuiiizRjxgwVFhbq559/1ksvvSRJCgwM9Kivzvemt+sSGxurqKgoPf/88/rHP/6hF154QZ06ddKECROqfGxoBCygEUtJSbEkWYcOHap0++WXX27169fP/vqTTz6xJFndunWzioqK7PF58+ZZkqydO3faY/Hx8VaHDh085uvQoYMVFxfnMZaammpJst5++217rLi42IqOjrYCAwMtt9ttWZZl7du3z5JkuVwuKz8/v9LHMX78eHuspKTEat++veXj42PNmTPHHj9y5IgVEBBgxcfHn3NtcnJyLEnWgw8+6DE+fPhwS5KVkpJijy1dutSSZO3bt8+yLMtatWqVJcn64osvzjr/oUOHKsxTLj4+3pJkTZs2rdJtp69r+boEBARYP//8sz2+detWS5KVmJhoj/Xr18/j+TzbnOfqrXyty5Wv07hx4zzqpkyZYkmyNm7caI916NDBkmR9+umn9lh+fr7ldDqthx9+uMK+zlTZ66f8NXnJJZdYx48ft8eLi4utNm3aWN27d7dOnDhhj69Zs8aSZCUnJ3s8fknWs88+a4+Vv058fHysjIwMe3zPnj1nXZvT3XnnnZYk68iRI1U+LsuyrHvuucdq166dVVpaao9t377dkmQtXbrUHouLi6vwfWVZ1f/erMm6PPnkkx77+tOf/mRFRkZW63Gh4eOjMaASY8aM8Tgu57rrrpMk/fDDD17PtXbtWoWGhuqee+6xx5o2baq//OUv+v3337V582aP+sGDB9sf3Zxp3Lhx9r/9/PzUu3dvWZalsWPH2uPBwcHq0qVLlb2uXbtWkvSXv/zFY7w6B6mW/0a9Zs0anTp1qsr6s/HmN+5Bgwbpoosusr/u27evoqKi7MdRW8rnT0pK8hgvf9cmMzPTYzwiIsJ+vUj/elenOs9HVeLj4xUQEGB//eWXXyo/P18PPvig/P397fG4uDh17dq1Ql+S5+un/HXSvHlz3X333fZ4ly5dFBwcXGW/5R/NtWjRolr9jxo1SgcPHtQnn3xij6WnpysgIKDSj9bOpqrvzZqsywMPPODx9XXXXfeHny80HAQhGK+ya8ZcfPHFHl9fcMEFkqQjR454Pf9PP/2kSy+9tMKBrd26dbO3n+5cH0ec2Vf56cqtWrWqMF5Vrz/99JN8fX3VqVMnj/EuXbqc836S1K9fPw0ePFhPPPGEWrVqpTvuuENLly6tcMzMuTRp0kTt27evdv2ll15aYeyyyy6r9Wsbla9T586dPcZDQ0MVHBxc4fk78zmS/vX6qclr53Rnvi7K91vZ89W1a9cKffn7+1cI2EFBQWrfvn2F74HqvH5cLpck6ejRo9Xq/89//rPatm2r9PR0Sf861uedd97RHXfcUe0wJVX9vXk+1uV8PF9oOAhCaNTKfyM8ceJEpduPHz/u8VtjubOdIWVZ1vlr7ixO/63/TJX1VR+9ll/YLisrSxMnTtQvv/yi++67T5GRkfr999+rNYfT6TzvZz2d7UKI5+PA1+peZLG2no9zvS6q42x91bTfrl27SvrXgdbV3f/w4cP1t7/9TSdPntQnn3yigwcP6t57763W/f9ov97OB3MQhNCodejQQZK0d+/eCtuOHz+uAwcO2DW12cO3335b4do4e/bs8eixrnXo0EFlZWX6/vvvPcYrW6uzueqqq/TMM8/oyy+/VHp6unbv3q2MjAxJ1Q8O1fXtt99WGPvmm288ziq64IILVFBQUKHuzHcBvOmtfJ3O3H9eXp4KCgrq9fmTKn++9u7dW+t93XbbbZKkt99+u9r3GTVqlNxutz788EOlp6erdevWio2N9aj5o6+b+l4XNDwEITRqN910kxwOhxYtWlQhiLz22msqKSnRLbfcUqs9DBw4ULm5uVqxYoU9VlJSoldeeUWBgYHq169fre7/bMof98svv+wxXp2r+h45cqTCb+C9evWSJPvjsfKzwCoLJjWxevVqj9Oft23bpq1bt3o8f506ddKePXt06NAhe+x///d/9fnnn3vM5U1vAwcOlFRxXV588UVJqtZZdrWhd+/eatOmjRYvXuzxkeRHH32kr7/+utb7io6O1s0336wlS5Zo9erVFbYXFxdrypQpHmM9evRQjx49tGTJEv3tb3/TsGHDKlyzqXnz5iosLKxxX/W9Lmh4OH0ejVqbNm2UnJysmTNn6vrrr9ftt9+uZs2aacuWLXrnnXc0YMAA+zfb2jJ+/Hi9+uqrGj16tLKzsxUeHq733ntPn3/+uVJTU706PuJ86tWrl+655x4tXLhQhYWFuvrqq7Vhw4ZKr8lypmXLlmnhwoW688471alTJx09elSvv/66XC6XHRwCAgIUERGhFStW6LLLLlPLli3VvXt3de/evUb9du7cWddee60mTJigoqIipaam6sILL9Sjjz5q19x333168cUXFRsb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r76yxo8fbwUHB1u5ubmWZVnWyJEjrWnTptn1n3/+udWkSRPr+eeft77++msrJSWF0+erwdt1njNnjuVwOKz33nvP+vXXX+3b0aNH6+shNAjervOZOGuserxd5/3791stWrSwJk6caO3du9das2aN1aZNG+vpp5+ur4fQIHi7zikpKVaLFi2sd955x/rhhx+sv//971anTp2su+++u74eQoNw9OhRa8eOHdaOHTssSdaLL75o7dixw/rpp58sy7KsadOmWSNHjrTry0+ff+SRR6yvv/7aWrBgAafPN3SvvPKKdfHFF1sOh8Pq27ev9c9//tPe1q9fPys+Pt6j/t1337Uuu+wyy+FwWJdffrmVmZlZxx03TN6sc4cOHSxJFW4pKSl133gD4+3r+XQEoerzdp23bNliRUVFWU6n07rkkkusZ555xiopKanjrhseb9b51KlT1qxZs6xOnTpZ/v7+VlhYmPXggw9aR44cqfvGG5BPPvmk0v9vy9c2Pj7e6tevX4X79OrVy3I4HNYll1xiLV26tNb79LEs3tcDAABm4hghAABgLIIQAAAwFkEIAAAYiyAEAACMRRACAADGIggBAABjEYQAAICxCEIAAMBYBCEAAGAsghAAADAWQQgAABjr/wEkhhcxa05XQAAAAABJRU5ErkJggg==", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "print(\"Performances of MT64 implemented in Cython\")\n", "%timeit cython_uniform_mt(shape)\n", "fig, ax = subplots()\n", "ax.hist(cython_uniform_mt(shape).ravel())\n", "ax.set_title(\"Uniform distribution from Cython\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Eureka ! This cython implementation is as fast as the numpy one, and much faster than the C or C++ implementations.\n", "\n", "Well I don't like the global variable and minor things in this code, but with just 50 lines of code, my problem is finding a solution ! Thanks Anandh Swaminathan for sharing this code.\n", "\n", "Here is the re-written version with a class to allow to have multiple generators with different seeds (Well, not that robust, but better than sharing the same seed and creating a bottleneck for the random number generation)." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "%%cython\n", "# cython: boundscheck=False\n", "# cython: cdivision=True\n", "# cython: wraparound=False\n", "\n", "import cython\n", "import time\n", "import numpy\n", "from libc.stdlib cimport RAND_MAX\n", "from libc.stdint cimport uint32_t, uint64_t\n", "from libc.math cimport log, sqrt, cos, M_PI\n", "\n", "#Few constants for 64-bit Mersenne Twisters\n", "cdef:\n", " uint32_t NN=312\n", " uint32_t MM=156\n", " uint64_t MATRIX_A=0xB5026F5AA96619E9ULL\n", " uint64_t UM=0xFFFFFFFF80000000ULL # Most significant 33 bits\n", " uint64_t LM=0x7FFFFFFFULL #Least significant 31 bits\n", " double EPS64 = numpy.finfo(numpy.float64).eps\n", " double TWO_PI = 2.0 * M_PI \n", " double NRM53 = 1.0/((1<<53)-1) # normalization factor for uniform\n", "\n", "cdef class MT:\n", " \"\"\"\n", " This class implements 64-bit Mersenne Twisters\n", " \n", " http://www.math.sci.hiroshima-u.ac.jp/m-mat/MT/VERSIONS/C-LANG/mt19937-64.c\n", " \n", " Inspired from:\n", " https://github.com/ananswam/cython_random\n", " with minor clean-ups\n", " \n", " Licence: MIT\n", " \"\"\"\n", " cdef:\n", " uint64_t mt[312]\n", " uint32_t mti\n", " uint64_t mag01[2]\n", " bint has_spare\n", " double spare\n", " \n", " def __init__(self, seed):\n", " self.mti = NN + 1\n", " self._seed( seed)\n", " \n", " cdef inline void _seed(self, uint64_t seed) noexcept nogil:\n", " self.mt[0] = seed\n", " for self.mti in range(1, NN):\n", " self.mt[self.mti] = (6364136223846793005ULL * (self.mt[self.mti-1] ^ (self.mt[self.mti-1] >> 62)) + self.mti)\n", " self.mag01[0] = 0ULL\n", " self.mag01[1] = MATRIX_A\n", " self.mti = NN\n", " self.has_spare = False\n", " \n", " cdef inline uint64_t genrand64(self) noexcept nogil:\n", " cdef: \n", " uint32_t i\n", " uint64_t x\n", " if self.mti >= NN:\n", " for i in range(NN - MM):\n", " x = (self.mt[i]&UM) | (self.mt[i+1]&LM)\n", " self.mt[i] = self.mt[i+MM] ^ (x>>1) ^ self.mag01[int(x&1ULL)]\n", "\n", " for i in range(NN-MM, NN-1):\n", " x = (self.mt[i]&UM)|(self.mt[i+1]&LM)\n", " self.mt[i] = self.mt[i+(MM-NN)] ^ (x>>1) ^ self.mag01[int(x&1ULL)]\n", "\n", " x = (self.mt[NN-1]&UM)|(self.mt[0]&LM)\n", " self.mt[NN-1] = self.mt[MM-1] ^ (x>>1) ^ self.mag01[int(x&1ULL)]\n", " self.mti = 0\n", "\n", " x = self.mt[self.mti]\n", " self.mti += 1\n", " x ^= (x >> 29) & 0x5555555555555555ULL\n", " x ^= (x << 17) & 0x71D67FFFEDA60000ULL\n", " x ^= (x << 37) & 0xFFF7EEE000000000ULL\n", " x ^= (x >> 43);\n", " return x\n", " \n", " def rand(self):\n", " return self.genrand64()%(RAND_MAX+1ULL)\n", " \n", " cdef inline double _uniform(self) noexcept nogil:\n", " return (self.genrand64() >> 11) * NRM53\n", " \n", " def uniform(self):\n", " \"Return a random value between [0:1[\"\n", " return self._uniform()\n", " \n", " cdef inline double _normal_bm(self, double mu, double sigma) noexcept nogil:\n", " cdef:\n", " double u1=0.0, u2=0.0\n", " \n", " while (u1 == 0.0 ):\n", " u1 = self._uniform()\n", " u2 = self._uniform()\n", "\n", " return sigma * sqrt(-2.0 * log(u1)) * cos(TWO_PI * u2) + mu;\n", "\n", " def normal_bm(self, mu, sigma): \n", " \"\"\"\n", " Calculate the gaussian distribution using the Box–Muller algorithm\n", "\n", " Credits:\n", " https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform\n", "\n", " :param mu: the center of the distribution\n", " :param sigma: the width of the distribution\n", " :return: random value\n", " \"\"\" \n", " return self._normal(mu, sigma)\n", " \n", " cdef inline double _normal_m(self, double mu, double sigma) noexcept nogil:\n", " cdef: \n", " double u1=0.0, u2=0.0, s=0.0\n", " if self.has_spare:\n", " self.has_spare = False\n", " return mu + self.spare * sigma \n", " else:\n", " while (s>=1.0 or s<=0.0):\n", " u1 = 2.0 * self._uniform() - 1.0\n", " u2 = 2.0 * self._uniform() - 1.0\n", " s = u1 * u1 + u2 * u2;\n", " s = sqrt(-2.0*log(s)/s)\n", " self.spare = u2 * s\n", " self.has_spare = True\n", " return mu + sigma * u1 * s;\n", " \n", " def normal_m(self, mu, sigma):\n", " \"\"\"Implement Marsaglia polar method\n", " https://en.wikipedia.org/wiki/Marsaglia_polar_method\n", " \"\"\"\n", " return self._normal_m(mu, sigma)\n", "\n", "\n", "def cython_uniform_mtc(shape, seed=None):\n", " cdef: \n", " uint64_t size = numpy.prod(shape), idx\n", " double[::1] ary = numpy.empty(size)\n", " MT mt = MT(time.time_ns() if seed is None else seed)\n", " with nogil:\n", " for idx in range(size):\n", " ary[idx] = mt._uniform()\n", " return numpy.asarray(ary).reshape(shape) \n", "\n", "def cython_normal_bm_mtc(mu, sigma, seed=None):\n", " shape = mu.shape\n", " assert mu.shape == sigma.shape\n", " cdef: \n", " uint64_t size = numpy.prod(shape), idx\n", " double[::1] ary = numpy.empty(size)\n", " double[::1] cmu = numpy.ascontiguousarray(mu, dtype=numpy.float64).ravel()\n", " double[::1] csigma = numpy.ascontiguousarray(sigma, dtype=numpy.float64).ravel()\n", " MT mt = MT(time.time_ns() if seed is None else seed)\n", " \n", " with nogil:\n", " for idx in range(size):\n", " ary[idx] = mt._normal_bm(cmu[idx], csigma[idx])\n", " return numpy.asarray(ary).reshape(shape) \n", "\n", "def cython_normal_m_mtc(mu, sigma, seed=None):\n", " shape = mu.shape\n", " assert mu.shape == sigma.shape\n", " cdef: \n", " uint64_t size = numpy.prod(shape), idx\n", " double[::1] ary = numpy.empty(size)\n", " double[::1] cmu = numpy.ascontiguousarray(mu, dtype=numpy.float64).ravel()\n", " double[::1] csigma = numpy.ascontiguousarray(sigma, dtype=numpy.float64).ravel()\n", " MT mt = MT(time.time_ns() if seed is None else seed)\n", " \n", " with nogil:\n", " for idx in range(size):\n", " ary[idx] = mt._normal_m(cmu[idx], csigma[idx])\n", " return numpy.asarray(ary).reshape(shape) " ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Performances of the cdef class for Mersenne-twisters implemented in Cython\n", "30.1 ms ± 392 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] }, { "data": { "image/png": "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GysxMVGvv/666tSpo9jYWM2aNYvzg34BghDKrPAvwpMnTxa7/MSJE8VewXGuK6TMGSdGlpcz/xo7W3F9VUavhTe2S0lJ0ciRI/Xjjz9q8ODBioqK0rFjx0q1DrfbfdGvejrXX7klnfj6S9Z9tvJ6P863X5TGufq60H6bNWsm6b8nWpd2+3379tU//vEPnTp1Sp9++qkOHjyoP/zhD6V6/i/tt6zrK43rrrtOP/74o/bs2aN169apS5cu8vHx0XXXXad169Zp/fr1Kigo+MVBqKK/x++55x699tprGj58uN599119/PHHWrZsmSR53drinnvu0Z49e/TSSy+pfv36evbZZ9WyZUt99NFHTs3zzz+vf//733rsscd08uRJ3X///WrZsmWxF2WgZAQhlFnDhg0lSbt37y6y7MSJE9q/f79TU549fPPNN0XujbNr1y6vHitaw4YNVVBQoO+++85rvLi5OpfOnTvr6aef1hdffKF58+Zpx44dWrBggaTSB4fSKvyo4Uxff/2111VFl112mTIzM4vUnX0UoCy9Fc7T2dtPT09XZmZmpb5/UvHv1+7du8u9r9tvv12S9NZbb5X6OQMGDFB2drY+/PBDzZs3T3Xr1lVsbKxXzS/dbypyXgoDzooVK7Rp0ybn6+uvv17r1q3TunXrFBgYqKioqPOup7zuZN60adMiV1aW5MiRI1q5cqXGjh2ryZMn684779Qtt9yiJk2aFFtfr1493XfffXr//fe1d+9e1a5dW08//bRXTevWrTV+/HitXbtW69at048//qg5c+Zc8OuyGUEIZXbzzTfL5XLplVdeKRJEXn31VeXl5el3v/tdufZw6623Ki0tTQsXLnTG8vLy9NJLLykoKEhdu3Yt1+2fS+HrPvNSX0mluqvvkSNHivw12q5dO0lyPh4rvAqsuGById5//32vy583btyoDRs2eL1/TZs21a5du3To0CFn7Msvv9Tnn3/uta6y9HbrrbdKKjovL7zwgiSV6iq78tChQweFhoZqzpw5Xh9JfvTRR9q5c2e59xUTE6Pf/va3ev311/X+++8XWZ6bm6uHHnrIa6xNmzZq06aNXn/9df3jH/9Qnz59ityzKTAw8Bd9dFKR89K4cWM1aNBA06dP1+nTp3XttddK+m9A+u6777R48WJ17ty5xPtSBQYGSrp43yuFevbsqS+//LLIlY3SuY8mFR59Onv52ft/fn5+kfcpNDRU9evXd+Y9OztbeXl5XjWtW7eWr69vmT5Gx/9w+TzKLDQ0VBMmTND48eN1/fXX6/e//71q1Kih9evX6+2331b37t2dv2zLy7Bhw/TXv/5VAwcOVGpqqho1aqTFixfr888/14wZM8p0fsTF1K5dO917772aPXu2srKydM0112jlypXF3pPlbHPnztXs2bN15513qmnTpjp69Khee+01eTweJzgEBASoRYsWWrhwoa666irVqlVLrVq1uqBzFiTpiiuu0HXXXacRI0YoJydHM2bMUO3atfXII484NYMHD9YLL7yg2NhYDRkyRBkZGZozZ45atmzpnNxb1t7atm2r+Ph4vfrqq8rMzFTXrl21ceNGzZ07Vz169NCNN954Qa/nl6pevbr+8pe/aNCgQeratavuvfde5zLxRo0aacyYMeXew9///nd1795dd911l26//XbdfPPNCgwM1DfffKMFCxbop59+KvL/ZA0YMMAJSMV9LBYVFaWFCxcqMTFRHTt2VFBQUJm+Ryt6Xrp06aIFCxaodevWzrk77du3V2BgoL7++mv17du3xHUUHjG6//77FRsbKz8/P/Xp0+cX9/bwww9r8eLFuvvuu52Prn/++Wd98MEHmjNnjtq2bVvkOR6Px7nFwunTp9WgQQN9/PHH2rt3r1fd0aNHFRERoV69eqlt27YKCgrSJ598ok2bNjn3MVu1apVGjhypu+++W1dddZXy8vL05ptvys/Pr0wnyOMMlXW5Gqq+t956y3Tu3NkEBgYat9ttmjVrZiZPnmxOnTrlVVd4ie7Zl3sWd4lpaS+fN8aY9PR0M2jQIFOnTh3jcrlM69atvdZ15jaeffbZIs8/120A4uPjTWBgYJH6rl27mpYtWxY3FV5Onjxp7r//flO7dm0TGBhobr/9drN///4SL5/fvHmzuffee83ll19u3G63CQ0NNbfddpv54osvvNa/fv16ExUVZVwul9c6z9V34bLiLp9/9tlnzfPPP28iIyON2+02Xbp0MV9++WWR57/11lumSZMmxuVymXbt2pnly5cX+16dq7ezL583xpjTp0+byZMnm8aNG5vq1aubyMhIM27cuCL7z7ne/3Nd1n+2810+f65LkBcuXGh+85vfGLfbbWrVqmX69evndZsBY8q+n5zrdRTnxIkT5rnn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BAAArEUQAgAA1iIIAQAAaxGEAACAtQhCAADAWgQhAABgLYIQAACwFkEIAABYiyAEAACsRRACAADWIggBAABrlTkIrV27Vrfffrvq168vHx8fvf/++17LjTGaMGGC6tWrp4CAAHXr1k3ffPONV83PP/+sfv36yePxKCQkREOGDNGxY8e8av7973+rS5cu8vf3V2RkpKZNm1akl0WLFqlZs2by9/dX69attXTp0jL3AgAA7FXmIHT8+HG1bdtWs2bNKnb5tGnT9OKLL2rOnDnasGGDAgMDFRsbq1OnTjk1/fr1044dO7RixQotWbJEa9eu1bBhw5zl2dnZ6t69uxo2bKjU1FQ9++yzmjRpkl599VWnZv369br33ns1ZMgQbdmyRT169FCPHj20ffv2MvUCAADs5WOMMRf8ZB8fvffee+rRo4ek/x6BqV+/vh588EE99NBDkqSsrCyFhYUpKSlJffr00c6dO9WiRQtt2rRJHTp0kCQtW7ZMt956qw4cOKD69evrlVde0eOPP660tDS5XC5J0tixY/X+++9r165dkqTevXvr+PHjWrJkidNP586d1a5dO82ZM6dUvZQkOztbwcHBysrKksfjudBpOqdGY5Mv+joBAKhKvp8ad9HXWZbf3xf1HKG9e/cqLS1N3bp1c8aCg4MVHR2tlJQUSVJKSopCQkKcECRJ3bp1k6+vrzZs2ODUXH/99U4IkqTY2Fjt3r1bR44ccWrO3E5hTeF2StPL2XJycpSdne31AAAAl66LGoTS0tIkSWFhYV7jYWFhzrK0tDSFhoZ6La9WrZpq1arlVVPcOs7cxrlqzlxeUi9nmzJlioKDg51HZGRkKV41AACoqrhq7Azjxo1TVlaW89i/f39ltwQAAMrRRQ1C4eHhkqT09HSv8fT0dGdZeHi4MjIyvJbn5eXp559/9qopbh1nbuNcNWcuL6mXs7ndbnk8Hq8HAAC4dF3UINS4cWOFh4dr5cqVzlh2drY2bNigmJgYSVJMTIwyMzOVmprq1KxatUoFBQWKjo52atauXavTp087NStWrNDVV1+tyy67zKk5czuFNYXbKU0vAADAbmUOQseOHdPWrVu1detWSf89KXnr1q3at2+ffHx8NHr0aD311FP64IMPtG3bNg0YMED169d3rixr3ry5fvvb3+qPf/yjNm7cqM8//1wjR45Unz59VL9+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5yeeeEKhoaEaMmRIRbRZ5V3IPH/wwQeKiYlRQkKCwsLC1KpVKz3zzDPKz8+vqLarnAuZ52uuuUapqanOx2d79uzR0qVLdeutt1ZIz7aorN+Dl/x/sVEZDh8+rPz8/CL/jUdYWJh27dpV7HPS0tKKrU9LSyu3Pqu6C5nnsz366KOqX79+kW8+/M+FzPNnn32m//u//9PWrVsroMNLw4XM8549e7Rq1Sr169dPS5cu1bfffqv77rtPp0+f1sSJEyui7SrnQua5b9++Onz4sK677joZY5SXl6fhw4frscceq4iWrXGu34PZ2dk6efKkAgICymW7HBGCtaZOnaoFCxbovffek7+/f2W3c8k4evSo+vfvr9dee0116tSp7HYuaQUFBQoNDdWrr76qqKgo9e7dW48//rjmzJlT2a1dUlavXq1nnnlGs2fP1ubNm/Xuu+8qOTlZTz75ZGW3houAI0LloE6dOvLz81N6errXeHp6usLDw4t9Tnh4eJnqcWHzXOi5557T1KlT9cknn6hNmzbl2WaVV9Z5/u677/T999/r9ttvd8YKCgokSdWqVdPu3bvVtGnT8m26CrqQ/blevXqqXr26/Pz8nLHmzZsrLS1Nubm5crlc5dpzVXQh8/znP/9Z/fv319ChQyVJrVu31vHjxzVs2DA9/vjj8vXlmMLFcK7fgx6Pp9yOBkkcESoXLpdLUVFRWrlypTNWUFCglStXKiYmptjnxMTEeNVL0ooVK85ZjwubZ0maNm2annzySS1btkwdOnSoiFartLLOc7NmzbRt2zZt3brVefz+97/XjTfeqK1btyoyMrIi268yLmR/vvbaa/Xtt986QVOSvv76a9WrV48QdA4XMs8nTpwoEnYKw6fh/y2/aCrt92C5noptsQULFhi3222SkpLMV199ZYYNG2ZCQkJMWlqaMcaY/v37m7Fjxzr1n3/+ualWrZp57rnnzM6dO83EiRO5fL4UyjrPU6dONS6XyyxevNj89NNPzuPo0aOV9RKqhLLO89m4aqx0yjrP+/btMzVr1jQjR440u3fvNkuWLDGhoaHmqaeeqqyXUCWUdZ4nTpxoatasad5++22zZ88e8/HHH5umTZuae+65p7JeQpVw9OhRs2XLFrNlyxYjybzwwgtmy5Yt5ocffjDGGDN27FjTv39/p77w8vmHH37Y7Ny508yaNYvL56u6l156yVx++eXG5XKZTp06mX/961/Osq5du5r4+Hiv+nfeecdcddVVxuVymZYtW5rk5OQK7rhqKss8N2zY0Egq8pg4cWLFN17FlHV/PhNBqPTKOs/r16830dHRxu12myZNmpinn37a5OXlVXDXVU9Z5vn06dNm0qRJpmnTpsbf399ERkaa++67zxw5cqTiG69CPv3002J/3hbObXx8vOnatWuR57Rr1864XC7TpEkT88Ybb5R7nz7GcFwPAADYiXOEAACAtQhCAADAWgQhAABgLYIQAACwFkEIAABYiyAEAACsRRACAADWIggBAABrEYQAAIC1CEIAAMBaBCEAAGCt/wcW5sTavDYqmgAAAABJRU5ErkJggg==", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "print(\"Performances of the cdef class for Mersenne-twisters implemented in Cython\")\n", "%timeit cython_uniform_mtc(shape)\n", "fig, ax = subplots()\n", "ax.hist(cython_uniform_mtc(shape).ravel())\n", "ax.set_title(\"Uniform distribution from Cython with class\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This version with a class is even slightly faster. Let's double check the results are corrects. But before, let's summarise the performances obtained:\n", "\n", "## Summary of the performances obtained \n", "\n", "### Against Python" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Performances of the raw random number generator\n", "===============================================\n", "Random module from Python: 47.355 ns\n", "MT written in Cython : 36.621 ns\n", "MT written in Numpy : 310.413 ns\n" ] } ], "source": [ "print(\"Performances of the raw random number generator\")\n", "print(\"=\"*47)\n", "mt = MT(0)\n", "x = MT19937()\n", "NRM = 1.0/(1<<31)\n", "timeit_random_random = %timeit -o -q random.random()\n", "print(f'{\"Random module from Python\":25s}: {timeit_random_random.best*1e9:.3f} ns')\n", "timeit_cython_uniform = %timeit -o -q mt.uniform()\n", "print(f'{\"MT written in Cython\":25s}: {timeit_cython_uniform.best*1e9:.3f} ns')\n", "timeit_mt_numpy = %timeit -o -q x.random_raw()*NRM\n", "print(f'{\"MT written in Numpy\":25s}: {timeit_mt_numpy.best*1e9:.3f} ns')" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Comparison of the performances of the various implementations:\n", "==============================================================\n", "Numpy from Python : 38.589 ms\n", "Cython with `rand` from C : 80.299 ms\n", "Cython with C++ random number generator (32bits) : 79.711 ms\n", "Cython with C++ random number generator (64bits) : 68.727 ms\n", "Cython with MT from Numpy'via C-API : 36.253 ms\n", "Cython with MT implemented in Cython (original) : 36.057 ms\n", "Cython with MT implemented in Cython (modified) : 29.507 ms\n" ] } ], "source": [ "# Comparison of the performances of the various implementations\n", "print(\"Comparison of the performances of the various implementations:\")\n", "print(\"=\"*62)\n", "timeit_numpy = %timeit -o -q numpy.random.random(shape)\n", "print(f'{\"Numpy from Python\":60s}: {timeit_numpy.best*1000:.3f} ms')\n", "timeit_rand = %timeit -o -q cython_uniform_rand(shape)\n", "print(f'{\"Cython with `rand` from C\":60s}: {timeit_rand.best*1000:.3f} ms')\n", "timeit_cpp32 = %timeit -o -q cython_uniform_cpp(shape)\n", "print(f'{\"Cython with C++ random number generator (32bits)\":60s}: {timeit_cpp32.best*1000:.3f} ms')\n", "timeit_cpp64 = %timeit -o -q cython_uniform64_cpp(shape)\n", "print(f'{\"Cython with C++ random number generator (64bits)\":60s}: {timeit_cpp64.best*1000:.3f} ms')\n", "timeit_cnp = %timeit -o -q cython_uniform_cnp(shape)\n", "print(f'{\"Cython with MT from Numpy'via C-API\":60s}: {timeit_cnp.best*1000:.3f} ms')\n", "timeit_mt0 = %timeit -o -q cython_uniform_mt(shape)\n", "print(f'{\"Cython with MT implemented in Cython (original)\":60s}: {timeit_mt0.best*1000:.3f} ms')\n", "timeit_mt1 = %timeit -o -q cython_uniform_mtc(shape)\n", "print(f'{\"Cython with MT implemented in Cython (modified)\":60s}: {timeit_mt1.best*1000:.3f} ms')" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Comparison performances for normal distributions:\n", "==================================================\n", "Distribution from Numpy : 188.471 ms\n", "Distribution from C++ (64 bits) : 248.917 ms\n", "Box-Muller tranformation : 181.559 ms\n", "Marsaglia transformation : 95.429 ms\n" ] } ], "source": [ "print(\"Comparison performances for normal distributions:\")\n", "print(\"=\"*50)\n", "z = numpy.zeros(shape)\n", "s = numpy.ones(shape)\n", "timeit_np_normal = %timeit -o -q numpy.random.normal(z, s)\n", "print(f'{\"Distribution from Numpy\":40s}: {1000*timeit_np_normal.best:.3f} ms')\n", "timeit_cpp_normal = %timeit -o -q cython_normal64_cpp(z, s)\n", "print(f'{\"Distribution from C++ (64 bits)\":40s}: {1000*timeit_cpp_normal.best:.3f} ms')\n", "timeit_bm_normal = %timeit -o -q cython_normal_bm_mtc(z, s)\n", "print(f'{\"Box-Muller tranformation\":40s}: {1000*timeit_bm_normal.best:.3f} ms')\n", "timeit_mt_normal = %timeit -o -q cython_normal_m_mtc(z, s)\n", "print(f'{\"Marsaglia transformation\":40s}: {1000*timeit_mt_normal.best:.3f} ms')" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4485690 4485690 4485690 4485690 4485690\n" ] } ], "source": [ "npt = 1000\n", "seed = 123 #time.time_ns()\n", "rng = numpy.random.Generator(numpy.random.MT19937(seed=seed))\n", "distrib_random_normal = rng.normal(z, s)\n", "distrib_normal64_cpp = cython_normal64_cpp(z, s, seed=seed)\n", "distrib_normal_bm_mtc = cython_normal_bm_mtc(z, s, seed=seed)\n", "distrib_normal_m_mtc = cython_normal_m_mtc(z, s, seed=seed)\n", "assert distrib_random_normal.shape == shape\n", "assert distrib_normal64_cpp.shape == shape\n", "assert distrib_normal_bm_mtc.shape == shape\n", "assert distrib_normal_m_mtc.shape == shape\n", "ref = numpy.histogram(distrib_random_normal.ravel(), npt)\n", "cpp = numpy.histogram(distrib_normal64_cpp.ravel(), npt)\n", "obt = numpy.histogram(distrib_normal_bm_mtc.ravel(), npt)\n", "obt2 = numpy.histogram(distrib_normal_m_mtc.ravel(), npt)\n", "print(numpy.prod(shape), ref[0].sum(), cpp[0].sum(), obt[0].sum(), obt2[0].sum())" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjkAAAHHCAYAAABdm0mZAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQAAmJFJREFUeJzs3Xd8FHX+x/HXbM9usukVAglJ6CUISlERhCMg6qHeKdjgxHaCvR13FkQ9KypnPc8TPJWfyCnoWcCIFIWgtNBrSAglIZCQbPq2+f0RsrKGkkDCpHyej8c+yM58Z+a9m7IfvvOd7yiqqqoIIYQQQrQyOq0DCCGEEEI0BSlyhBBCCNEqSZEjhBBCiFZJihwhhBBCtEpS5AghhBCiVZIiRwghhBCtkhQ5QgghhGiVpMgRQgghRKskRY4QQgghWiUpcoT4DUVRmDZtWpPtf/bs2SiKQk5OTpMdo7nZtWsXI0eOJDg4GEVRWLBggdaRNDFt2jQURTnj7YcOHcrQoUN9z3NyclAUhdmzZ599uNM40c9tQkICl19+eZMfG2Dp0qUoisLSpUvPyfFE6yBFjmh2av+YnuyxatUqrSOKBpowYQKbNm3i2Wef5cMPP6R///4nbFf7oa0oCp999lmd9bVFwpEjR5o6cqv21ltvnZPC6Ew052yi5TFoHUCIk5k+fTqJiYl1licnJzfpcSsrKzEY5FejsVRWVpKRkcHf/vY3pkyZUu/tpk+fztVXX31WPR+tXceOHamsrMRoNDZou7feeouIiAgmTpxY721uuukmxo0bh9lsbmDKhjlZtiFDhlBZWYnJZGrS44vWRf6Si2Zr9OjRJ/0ff1OyWCzn/Jit2eHDhwEICQmp9zapqalkZmYyf/58rr766iZKBuXl5dhstibbf1NTFKXJf15r3yO9Xo9er2/SY52KTqeT303RYHK6SrRoxcXFTJw4keDgYEJCQpgwYQKZmZl1xin8dixDrYkTJ5KQkOC37PgxOf/9739RFIVly5bV2faf//wniqKwefNmADZu3MjEiRPp1KkTFouFmJgYbrnlFgoLC+v1Wr799lsuvvhibDYbQUFBjBkzhi1bttTJGxgYyIEDBxg7diyBgYFERkby0EMP4fF4/Np6vV5mzpxJr169sFgsREZGMmrUKNasWePX7qOPPqJfv34EBAQQFhbGuHHj2LdvX70yr1+/ntGjR2O32wkMDGT48OF+pxOnTZtGx44dAXj44YdRFKXO+30i48aNo3PnzkyfPh1VVU/bft68eb7XEBERwY033siBAwf82tS+d1lZWVx22WUEBQVxww03ADXf8ylTpjBv3jy6d+9OQEAAgwYNYtOmTUDN9zo5ORmLxcLQoUPrjKf68ccf+eMf/0iHDh0wm83Ex8dz//33U1lZedrsJ/Puu++SlJREQEAAF1xwAT/++GOdNicak5Ofn8+f/vQn2rdvj9lsJjY2lt///ve+zAkJCWzZsoVly5b5Tg3W/m7UnipetmwZd911F1FRUbRv395v3YnGkn333XekpqZisVjo3r07n3/+ud/6k41F+u0+T5XtZGNyGvK9r8/vzSeffEK/fv0ICgrCbrfTq1cvZs6cWSe7aBmkJ0c0WyUlJXXGXiiKQnh4OACqqvL73/+en376iTvvvJNu3boxf/58JkyY0GgZxowZQ2BgIJ9++imXXHKJ37q5c+fSo0cPevbsCUB6ejp79uzhT3/6EzExMWzZsoV3332XLVu2sGrVqlOedvnwww+ZMGECaWlpvPDCC1RUVPD2229z0UUXsX79er/CwOPxkJaWxoABA3j55Zf5/vvvmTFjBklJSfz5z3/2tZs0aRKzZ89m9OjR3Hrrrbjdbn788UdWrVrl6yF79tlnefzxx7n22mu59dZbOXz4MK+//jpDhgxh/fr1p+x92bJlCxdffDF2u51HHnkEo9HIP//5T4YOHcqyZcsYMGAAV199NSEhIdx///2MHz+eyy67jMDAwNO+73q9nscee4ybb775tL05s2fP5k9/+hPnn38+zz33HIcOHWLmzJmsWLGizmtwu92kpaVx0UUX8fLLL2O1Wn3rfvzxR7788ksmT54MwHPPPcfll1/OI488wltvvcVdd93F0aNHefHFF7nlllv44YcffNvOmzePiooK/vznPxMeHs4vv/zC66+/zv79+5k3b95pX+9v/fvf/+aOO+5g8ODB3HfffezZs4crr7ySsLAw4uPjT7ntNddcw5YtW7j77rtJSEigoKCA9PR0cnNzSUhI4LXXXuPuu+8mMDCQv/3tbwBER0f77eOuu+4iMjKSJ554gvLy8lMeb9euXVx33XXceeedTJgwgVmzZvHHP/6RhQsX8rvf/a5Br7s+2Y7XkO99fX5v0tPTGT9+PMOHD+eFF14AYNu2baxYsYJ77723Qa9FNBOqEM3MrFmzVOCED7PZ7Gu3YMECFVBffPFF3zK3261efPHFKqDOmjXLt/ySSy5RL7nkkjrHmjBhgtqxY0e/ZYD65JNP+p6PHz9ejYqKUt1ut29ZXl6eqtPp1OnTp/uWVVRU1Nn///3f/6mAunz58jqvLzs7W1VVVS0tLVVDQkLU2267zW/b/Px8NTg42G/5hAkTVMDvuKqqqn379lX79evne/7DDz+ogHrPPffUyeT1elVVVdWcnBxVr9erzz77rN/6TZs2qQaDoc7y3xo7dqxqMpnUrKws37KDBw+qQUFB6pAhQ3zLsrOzVUB96aWXTrm/37Z1u91qSkqK2qdPH1/mJ598UgXUw4cPq6qqqk6nU42KilJ79uypVlZW+vbz1VdfqYD6xBNP+JbVvnd/+ctf6hy39mer9nuiqqr6z3/+UwXUmJgY1eFw+JZPnTrV7/unqif+3j/33HOqoijq3r17fctq859K7WtKTU1Vq6urfcvfffddFfD7Oa59v2p/1o8ePVqv97pHjx4n/H2o/dm86KKL/H7ej193/Ovu2LGjCqifffaZb1lJSYkaGxur9u3b97Sv+0T7PFm2JUuWqIC6ZMkSVVXP7Ht/ut+be++9V7Xb7XVeu2i55HSVaLbefPNN0tPT/R7ffvutb/0333yDwWDw673Q6/XcfffdjZrjuuuuo6CgwK+b/L///S9er5frrrvOtywgIMD3dVVVFUeOHGHgwIEArFu37qT7T09Pp7i4mPHjx3PkyBHfQ6/XM2DAAJYsWVJnmzvvvNPv+cUXX8yePXt8zz/77DMUReHJJ5+ss21tj9Lnn3+O1+vl2muv9TtuTEwMKSkpJzxuLY/Hw3fffcfYsWPp1KmTb3lsbCzXX389P/30Ew6H46Tb10dtb86GDRtOesn5mjVrKCgo4K677vIbrzFmzBi6du3K119/XWeb439ejjd8+HC/HrMBAwYANT0jQUFBdZYf/34f/70vLy/nyJEjDB48GFVVWb9+/elf7Ale05133uk3yLb2tOypBAQEYDKZWLp0KUePHm3QcY9322231Xv8TVxcHFdddZXvud1u5+abb2b9+vXk5+efcYbTOZPv/el+b0JCQigvLyc9Pb3JcotzS4oc0WxdcMEFjBgxwu8xbNgw3/q9e/cSGxtb5/RHly5dGjXHqFGjCA4OZu7cub5lc+fOJTU1lc6dO/uWFRUVce+99xIdHU1AQACRkZG+q8NKSkpOuv9du3YBcOmllxIZGen3+O677ygoKPBrXzu+5nihoaF+H2pZWVnExcURFhZ2yuOqqkpKSkqd427btq3OcY93+PBhKioqTvhed+vWDa/XW+9xPadyww03kJycfNKxOXv37gVO/D3v2rWrb30tg8HgG2PyWx06dPB7XltQ/Pb0UO3y49/v3NxcJk6cSFhYmG+8R+3pzVN970+kNnNKSorfcqPR6FdQnojZbOaFF17g22+/JTo6miFDhvDiiy82uNg40VWNJ5OcnFznVGzt70VTzgXV0O99fX5v7rrrLjp37szo0aNp3749t9xyCwsXLmyC9OJckTE5ok1QFOWEH5K/HXR4ImazmbFjxzJ//nzeeustDh06xIoVK/j73//u1+7aa69l5cqVPPzww6SmphIYGIjX62XUqFF4vd6T7r923YcffkhMTEyd9b+9nL2xrnDxer0oisK33357wn3WZ+xMU6vtzZk4cSJffPHFWe/PbDaj0534/3Yne19Ptrz258nj8fC73/2OoqIiHn30Ubp27YrNZuPAgQNMnDjxlN/7pnDfffdxxRVXsGDBAhYtWsTjjz/Oc889xw8//EDfvn3rtY/je6Yaw8nGo9Xn96+x1Of3JioqiszMTBYtWsS3337Lt99+y6xZs7j55pv54IMPzkFK0dikyBEtVseOHVm8eDFlZWV+H8g7duyo0zY0NNSvW7rWb/+3dzLXXXcdH3zwAYsXL2bbtm2oqup3quro0aMsXryYp556iieeeMK3vLaX5lSSkpKAmj+wI0aMqFee+uxz0aJFFBUVnbQ3JykpCVVVSUxM9OuRqo/IyEisVusJ3+vt27ej0+lOO0C2vm688UaeeeYZnnrqKa688kq/dbVXbu3YsYNLL73Ub92OHTt865vSpk2b2LlzJx988AE333yzb/mZnvKozbxr1y6/1+RyucjOzqZPnz6n3UdSUhIPPvggDz74ILt27SI1NZUZM2bw0UcfAScvOs7E7t27UVXVb587d+4E8J3+Cw0NBWquhjx+MPCJfv/qm62pvvcmk4krrriCK664Aq/Xy1133cU///lPHn/88Safo0s0PjldJVqsyy67DLfbzdtvv+1b5vF4eP311+u0TUpKYvv27b45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vMB7773Hli1bCAsLY+zYsVxyySVs3LiRjIwMbr/9dl+RV1ZWxmWXXcbixYtZv349o0aN4oorriA3N9e3z5tvvpmDBw+ydOlSPvvsM959910KCgpO+bqO5/V6ad++PfPmzWPr1q088cQT/PWvf+XTTz+t9z4aU/PsXxJCtCpVVU5U9diMwm4IUiprVoTE15wu0popCACdojAi7ysyQ8LQFxcBUO2uRnW5UIxGLRM2GrfXzb82/UuTY9/W6zaM+tO/j6WlpcycOZM33niDCRMmAJCUlMRFF10EwNVXX82UKVP44osvuPbaa4Gano2JEyeiKAphYWHo9XqCgoKIiYnx27fL5eKdd94hKSkJgClTpjB9+nTf+pdffplHH32UcePGAfDCCy+wZMkSXnvtNd58801fu4ceeogxY8YA8NRTT9GjRw92795N165dT/nabrnlFh588EH+9re/8d///pekpCRSU1PrtLvvvvt8XyckJPDMM89w55138tZbb/m9lrfeeos+ffoAUFRURElJCZdffrnv9XXr1s3Xvk+fPr62AE8//TTz58/nyy+/ZMqUKWzfvp3vv/+e1atX079/fwDee+89UlJSTvmajmc0Gnnqqad8zxMTE8nIyODTTz/1fa/OJenJEUI0KW95OY73XqPaW9PVHh5gRKm9qqpdPw2THee4XqTe7YNp3ymRvYk1vQYHK/fgPa7HQDS9bdu2UV1dzfDhw0+43mKxcNNNN/H+++8DsG7dOjZv3szEiRNPu2+r1eorAABiY2N9PRUOh4ODBw9y4YUX+m1z4YUXsm2b/5WAvXv39tsHUK8ejzFjxlBWVsby5ct5//33T9iLA/D9998zfPhw2rVrR1BQEDfddBOFhYVUVFT42phMJr8cYWFhTJw4kbS0NK644gpmzpzp14tVVlbGQw89RLdu3QgJCSEwMJBt27b5enJ27NiBwWDgvPPO822TnJxMaGjoaV/X8d5880369etHZGQkgYGBvPvuu369ReeS9OQIIZrU3lWLySnbRaXXTaDZQIzB+evK2juCNwfdLodtX2EzGfhdz0jyDIk4DyzA6PHiraxEHxiodcJGYdAZuK3XbZoduz4CAgJO2+bWW28lNTWV/fv3M2vWLC699FI6dux42u2Mv+mRUxTljO5ofvx+ak8H1Wfgr8Fg4KabbuLJJ5/k559/Zv78+XXa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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fig, ax = subplots()\n", "ax.plot((ref[1][1:]+ref[1][:-1])/2, ref[0], \"-\", alpha=0.5, label=\"numpy\")\n", "ax.plot((obt[1][1:]+obt[1][:-1])/2, obt[0], \"-\", alpha=0.5, label=\"cython Box-Muller\")\n", "ax.plot((obt2[1][1:]+obt2[1][:-1])/2, obt2[0], \"-\", alpha=0.5, label=\"cython Marsaglia\")\n", "ax.plot((cpp[1][1:]+cpp[1][:-1])/2, cpp[0], \"-\", alpha=0.5, label=\"C++\")\n", "ax.set_xlabel(\"distance in sigma\")\n", "ax.set_title(\"Equivalence of Normal distributions\")\n", "ax.legend();" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkkAAAHHCAYAAACr0swBAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQAA9KJJREFUeJzsnXecFEX6h5+emc0Lu+QlsxIECaKoKKCIcrd4GDjPgOcpKOglzvMwnQqomAMKhpPzPBFPORFB5HcqggiigmQEyWlZls05Teyu3x8Te6YnbCJIPZ/PwEx3dVV1T+/Ut9/3rbcUIYRAIpFIJBKJRKLDdLI7IJFIJBKJRHIqIkWSRCKRSCQSiQFSJEkkEolEIpEYIEWSRCKRSCQSiQFSJEkkEolEIpEYIEWSRCKRSCQSiQFSJEkkEolEIpEYIEWSRCKRSCQSiQFSJEkkEolEIpEYIEWSRNLEPP744yiKcrK7ITmN6NGjBxMnTmzQsdnZ2SiKwrvvvuvbdiLvwcsvv5zLL7/c93nNmjUoisLHH398QtqfOHEiPXr0OCFtSc48pEiSSCLw7rvvoiiK75WYmEinTp3Iysri1Vdfpbq6uknaycvL4/HHH2f79u1NUt/JZt26dTz++ONUVFSc7K5IYuRUvgdP5b5Jft5IkSSRxMDMmTP5z3/+w5tvvslf/vIXAO69914GDhzIjh07dGWnTZuG1WqtV/15eXk88cQTP5tBYN26dTzxxBNSJJ0kTuQ9uGLFClasWFGvY+pLpL7961//Yt++fc3avuTMxXKyOyCRnA5cddVVXHDBBb7PDz/8MF9//TVXX3011157LXv27CEpKQkAi8WCxSL/tE5XNE3D4XCQmJh4srvSYE7EPVhXV0dycjLx8fHN2k404uLiTmr7kp830pIkkTSQK664gunTp3P06FHef/9933ajeJCVK1cyYsQI0tPTSU1N5eyzz+aRRx4B3DEcF154IQB33HGHz7XnjTH59ttvufHGG+nWrRsJCQl07dqVv/3tbyGWgokTJ5Kamsrx48cZN24cqamptGvXjvvvvx9VVXVlNU1jzpw5DBw4kMTERNq1a8eYMWPYvHmzrtz777/PkCFDSEpKonXr1owfP55jx45FvC6PP/44DzzwAACZmZm+88nOzgbA5XLx5JNP0rNnTxISEujRowePPPIIdrs96jWvzznW1tZy33330bVrVxISEjj77LN56aWXEELoyimKwpQpU/jggw/o378/CQkJLF++3Odq/e6777jnnnto164d6enp/P73v8fhcFBRUcHtt99Oq1ataNWqFQ8++GBI3S+99BLDhg2jTZs2JCUlMWTIkEbF6lRUVDBx4kTS0tJIT09nwoQJhta6pr4HL7/8cgYMGMCWLVu47LLLSE5O9h0bHJPkRVVVHnnkETIyMkhJSeHaa68NuXfCxWIF1hmtb0YxSfX97pcuXcqAAQNISEigf//+LF++PKRPkjMT+bgrkTSC2267jUceeYQVK1Zw1113GZbZtWsXV199NYMGDWLmzJkkJCRw8OBBvv/+ewD69evHzJkzmTFjBnfffTeXXnopAMOGDQNg0aJF1NXV8cc//pE2bdqwceNGXnvtNXJzc1m0aJGuLVVVycrKYujQobz00kt89dVXzJo1i549e/LHP/7RV27SpEm8++67XHXVVUyePBmXy8W3337LDz/84LOYPf3000yfPp2bbrqJyZMnU1xczGuvvcZll13Gtm3bSE9PNzzf66+/nv379/Pf//6XV155hbZt2wLQrl07ACZPnsz8+fO54YYbuO+++9iwYQPPPvsse/bs4ZNPPol6zWM5RyEE1157LatXr2bSpEkMHjyYL7/8kgceeIDjx4/zyiuv6Or8+uuv+eijj5gyZQpt27alR48ePtfOX/7yFzIyMnjiiSf44YcfeOutt0hPT2fdunV069aNZ555hs8//5wXX3yRAQMGcPvtt/vqnTNnDtdeey233norDoeDDz/8kBtvvJH//e9/jB07Nuq5BiKE4LrrruO7777jD3/4A/369eOTTz5hwoQJUY9t7D0IUFpaylVXXcX48eP53e9+R4cOHSK2+fTTT6MoCg899BBFRUXMnj2b0aNHs337dp/VNRZi6Vsg9f3uv/vuO5YsWcKf/vQnWrRowauvvspvfvMbcnJyaNOmTcz9lPxMERKJJCzz5s0TgNi0aVPYMmlpaeK8887zfX7sscdE4J/WK6+8IgBRXFwcto5NmzYJQMybNy9kX11dXci2Z599ViiKIo4ePerbNmHCBAGImTNn6sqed955YsiQIb7PX3/9tQDEPffcE1KvpmlCCCGys7OF2WwWTz/9tG7/zp07hcViCdkezIsvvigAceTIEd327du3C0BMnjxZt/3+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+N3F0bMrB5LoqoL/TYVeV0Lfsc3RPYlEcoohLUkSieSMo7DKFnZfuFiVwRUroeo4bH2vubolkUhOMaRIkkgkkhgwC9fJ7oJEIjnBSJEkkUh+1igxrvcVDTkZSiI585AiSSKRSAIQCExq6GrsigzdPuN59913SU9PP9ndOO1RFIWlS5cC7qzniqLUaxHcE4kUSRKJ5LQntzqXclt5k9QVX1rNmLd2MmBtbsBWKZDONHr06MHs2bNPdjd89OjRA0VR+PDDD0P29e/fH0VRePfdd098xxpJ165dyc/P162qcSohRZJEIjmtKbGW8NzG55j+/fQmqa/1OvcMuG67SoP21F8ome0ORKhRSiJpEF27dmXevHm6bT/88AMFBQWkpKQ0qm5VVdG0E3+zms1mMjIysFhOzcn2UiRJJJLTmmPVx+p9TKQoJUVrGqtRy4pi+q5ci7qhzLdN1YTM9HyC0DSNF154gV69epGQkEC3bt14+umnAbjiiitClsoqLi4mPj6eVatWcfnll3P06FH+9re/oShKSFzbl19+Sb9+/UhNTWXMmDHk5+fr2p05cyZdunQhISHBt16pF697acmSJYwaNYrk5GTOPfdc1q9fH/Wcbr31Vr755huOHfPf8++88w633npriMh4+eWXGThwICkpKXTt2pU//elP1NTU+PZ7XYfLli3jnHPOISEhgZycHNasWcNFF11ESkoK6enpDB8+nKNHjwJw6NAhrrvuOjp06EBqaioXXnghX331la7d/Px8xo4dS1JSEpmZmSxYsCCiVS7Y3aaqKpMmTSIzM5OkpCTOPvts5syZE/XaNBdSJEkkktMaVat/YsjINI2I6X50l7u2cgcAVTYnf/nvVt5aezjicTmldVTZnE3SBy/Hqo9xpPJIk9QlhMCu2k/Kqz4C8+GHH+a5555j+vTp7N69mwULFvgWVp88eTILFizAbrf7yr///vt07tyZK664giVLltClSxdmzpxJfn6+TgTV1dXx0ksv8Z///Ie1a9eSk5PD/fff79s/Z84cZs2axUsvvcSOHTvIysri2muv5cCBA7r+Pfroo9x///1s376dPn36cMstt+ByRZ5B2aFDB7Kyspg/f76vLwsXLuTOO+8MKWsymXj11VfZtWsX8+fP5+uvv+bBBx/Ulamrq+P555/n7bffZteuXbRu3Zpx48YxcuRIduzYwfr167n77rt9IrGmpoZf/epXrFq1im3btjFmzBiuueYacnJyfHXefvvt5OXlsWbNGhYvXsxbb71FUVFRxPMKRNM0unTpwqJFi9i9ezczZszgkUce4aOPPoq5jqbk1LRvSSQSSYyIJo4XUprU4+Dv27qDJdidGhuPlPH7kT0NS2eX1PLk/3YD8O+JFzZND4Tg+Y3PA/D8Zc+TEtc4t4xDc3Dfmvuaomv1Ztbls0gwJ0QtV11dzZw5c3j99dd9C5n37NmTESNGAHD99dczZcoUPv30U2666SbAbVmZOHEiiqLQunVrzGYzLVq0ICMjQ1e30+lk7ty59Ozp/g6nTJmiW2z9pZde4qGHHmL8+PEAPP/886xevZrZs2fzxhtv+Mrdf//9jB3rTkr6xBNP0L9/fw4ePEjfvn0jntudd97Jfffdx6OPPsrHH39Mz549GTx4cEi5e++91/e+R48ePPXUU/zhD3/gH//4h+5c/vGPf3DuuecCUFZWRmVlJVdffbXv/Pr16+crf+655/rKAjz55JN88sknLFu2jClTprB3716++uorNm3axAUXXADA22+/Te/evSOeUyBxcXE88cQTvs+ZmZmsX7+ejz76yPddnUikJUkikZzWNL37Kkx99W0mpF/RUxHsLaiqZyP1o8revPWfKuzZswe73c6VV15puD8xMZHbbruNd955B4CtW7fy008/MXHixKh1Jycn+wQEQMeOHX2WkqqqKvLy8hg+fLjumOHDh7Nnzx7dtkGDBunqAGKyuIwdO5aamhrWrl3LO++8Y2hFAvjqq6+48sor6dy5My1atOC2226jtLSUuro6X5n4+HhdP1q3bs3EiRPJysrimmuuYc6cOTorWk1NDffffz/9+vUjPT2d1NRU9uzZ47Mk7du3D4vFwvnnn+87plevXrRq1SrqeQXyxhtvMGTIENq1a0dqaipvvfWWzlp1IpGWJIlEclqjiga42yLkTmoq0aUE/HsyaWpLW7wpnlmXz2rSOuvTdiwkJSVFLTN58mQGDx5Mbm4u8+bN44orrqB79+hL1cTFxek+K4rSoHsmsB6vOyuWwGmLxcJtt93GY489xoYNG/jkk09CymRnZ3P11Vfzxz/+kaeffprWrVvz3XffMWnSJBwOB8nJyYD7OgXHW82bN4977rmH5cuXs3DhQqZNm8bKlSu5+OKLuf/++1m5ciUvvfQSvXr1IikpiRtuuAGHw1Hv8w/Hhx9+yP3338+sWbO45JJLaNGiBS+++CIbNmxosjbqg7QkSSSS05qmtiQZBW4roqFSw39UE+W0rDdawPS6pkisqSgKCeaEk/KKtf+9e/cmKSmJVatWhS0zcOBALrjgAv71r3+xYMGCEItMfHw8qlo/Ad6yZUs6derE999/r9v+/fffc84559SrrkjceeedfPPNN1x33XWGVpotW7agaRqzZs3i4osvpk+fPuTl5cVc/3nnncfDDz/MunXrGDBgAAsWLPCdx8SJE/n1r3/NwIEDycjIIDs723fc2WefjcvlYtu2bb5tBw8epLw89vQc33//PcOGDeNPf/oT5513Hr169eLQoUMxH9/USEuSRCI5rdGI/PSt1NeaE1Z01TNYKaiek29TOnNITEzkoYce4sEHHyQ+Pp7hw4dTXFzMrl27mDRpkq/c5MmTmTJlCikpKfz617/W1dGjRw/Wrl3L+PHjSUhIoG3btjG1/cADD/DYY4/5YoXmzZvH9u3b+eCDD5rs/Pr160dJSYnPIhRMr169cDqdvPbaa1xzzTV8//33zJ07N2q9R44c4a233uLaa6+lU6dO7Nu3jwMHDnD77bcDbvG5ZMkSrrnmGhRFYfr06TrrV9++fRk9ejR33303b775JnFxcdx3332GFqtw9O7dm/fee48vv/ySzMxM/vOf/7Bp0yYyMzNjOr6pkZYkiURyWqNFSkSkugx/nCP+XDepu+3k09TuttOF6dOnc9999zFjxgz69evHzTffHBLzc8stt2CxWLjllltITEzU7Zs5cybZ2dn07NmTdu3axdzuPffcw9SpU7nvvvsYOHAgy5cvZ9myZfUKXo6FNm3ahHUrnnvuubz88ss8//zzDBgwgA8++IBnn302ap3Jycns3buX3/zmN/Tp04e7776bP//5z/z+978H3GkFWrVqxbBhw7jmmmvIysrSxR8BvPfee3To0IHLLruMX//619x11120aNEi5PqG4/e//z3XX389N998M0OHDqW0tJQ//elPMR3bHChCJu1oMFVVVaSlpVFZWUnLli1PdnckkjMKZ2Ehpf96m0NDOvDf+O0AvH7l6/4CFTnwxUPcn6Ria9EeAOsx90ynAZ3T+Nsv+hjWu2L6nbj2uKdrD2w7gB+PVSAUBeXqblzd0jON/rcLo/bv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yPE/jW6u+3HK5ow09o5s9FOiJckJWxxT7InVwmDtR3fRrftt6Q+chE3AFaQj8/Ds48i0d1uGPEntHqevTLvryX4mGS2NBc1WN6mYokP5cemKfE+Qsw2N141NbE9Yum9f3RZty5Y+IRLu31Hh5tVRdbR+2Y6uqfTutuTR0W2xnhm6Imy52Dyv89Sigk75oQRh36VPLUlbt25lwYIFFBQUUFRUxMKFC9mxY0faOu3t7SxatIjS0lKCwSDz5s1j06ZNtjLr169n7ty5BAIBKioquOSSS+ju7jbP//nPf+boo4+mvLycgoICmpubefLJJ21tXHPNNZGNIC2PhoaG3rt4QegrMkyyata1vOWfxt+KvoWmeuF3UMVYy4ukcF7CiZaRxJcphmvNn2M42BTsYiae5NCDwkspZb6KDAPPnVR5knZreWzXi9lS5E0am0ojkpzQNaLpClKrNYPstiVxCtz2aB6LJSnybxjNtNwVhAPkhzWUgpI8L8PK8hhdlR93tznlQ3KwfoUt4w9rymaRtFoJNaXx6txhvD5rCKqkKPNFCcI+RJ+KpAULFrB69WqWLVvGo48+yvPPP895552Xts6FF17II488woMPPsjy5cvZuHEjJ510knk+FAoxd+5cOjs7WbFiBXfffTdLly7lqquuMss8//zzHH300Tz22GOsWrWKI444gq9+9au8/vrrtr7GjRvHZ599Zj5eeOGF3r0BgtDHGCTKFgMKqnmu4Gts14ttv+6TyNbTYQ0yd8iOaKAybiHi5FZJl2ogsgTeYqmwuNu6MHBTELfI5OLp6mGQUMSlqNHp0XngpHoePi4eH6QMg66ouy1xw11HS1LULep3tEpZ6lquud2r89rBh1jORu9ooiBV4LKsNoyvbtMsx3TO21ZMrHqB341b19CI55hKwmePGzIMw5auK6yr6HUlo5Riy5ACPhtZnO5yBWGfpM9E0rvvvssTTzzB73//e5qamjj88MO55ZZbeOCBB9i4caNjnba2Nu68805uuOEGjjzySBobG7nrrrtYsWIFL730EgBPPfUU77zzDvfccw+TJk1izpw5/PSnP+XWW2+lszOyHvWmm27i0ksvZerUqYwcOZL/+I//YOTIkTzyyCO2/lwuF1VVVeajrKysr26HIPQaybE8qVVCKI1KMtebOfjUNniGAtDhTr832oVHj0paiJ4sRFKMwSICEq8grOy1ElvIdp/aRLLf4Db1qXa/iy63omtUXrRo2Azc/neDmy1l1hxByfVThVclplEw3D7zWFjBByPGpR3ma5OmRI5ZPh+mJUlpZkxSqij7xM+V6YYtqAF/SfKgrXsD6gqvO7J/W3nQHmAucUfCQKbPRNLKlSspKipiypQp5rFZs2ahaRovv/yyY51Vq1bR1dXFrFmzzGMNDQ0MGTKElStXmu1OmDCBysp4PprZs2ezbds2Vq9e7dhuOBxm+/btlJTY/9Dff/99ampqqK+vZ8GCBaxfvz7tNXV0dLBt2zbbQxD2ZcJpTUkRnGKSHiv8Jn8vOJFtvkEJZcE6s46oCBJGs1l9kidF50nS6m7LvOzMUs82nsiJ+Cqv9A1l3LstzbnEHc9CQwLRV3F327qRHp47PLICLqx0ulTyFi+pLC5J+OICNWJ7SnCDJXk5I2Pwu/wOeZK0TInUbQHWX7rKHccRw8DAQ9wyFNYiuwV6XXo0h5N1XM7XGzikCYDgUUemH5gg9CN9JpJaWlqoqLDHDLhcLkpKSmhpaUlZx+PxUFRUZDteWVlp1mlpabEJpNj52Dknrr/+enbs2MEpp5xiHmtqamLp0qU88cQT3H777Xz00UdMnz6d7du3p7ym6667jsLCQvNRWyv5PYT+Z7dmCYaNJo3Mj2ZAbqhObwkCKPAkl2nXArzrb0wdUGTBUPZl5bHnSkUEgaYsVgrDMren2VU+rBLcbamsL1Hqwh6GKS+17mDasSqMjK7BTDhJwNgyeiudykuXVmNb+Vfod2dtV0nWc8k923JmqYhFL8+dx+SiEzm0fTi27EVKObTqPBp7GoDkMgbgUWX4qYq8TpOeIdUqxpJvfYuKiy+i6MQTU9YVhP4mZ5F0+eWXJwU8Jz7ee++9vhhrj7jvvvv4yU9+wp/+9CebaJszZw4nn3wyEydOZPbs2Tz22GO0trbypz/9KWVbV1xxBW1tbebjk08+2RuXIAhpCSudp8ZeB9/4A0StM7/8xkHccMqkpH2srL/qh+onMqp4FAvGLEjZdrKbK3nqTrXpq66paNJDZ/ONsk2syjK2SPlSdSga7ugRp5gmS3AwEERjvKeM+sJhjv1Zms54PgttGMkPNLmIt6fO4Isi521fPKrcHPmwsjyGlaVb3WW9RqeBKkJmsLSKWpcsNSwr2Gr94xkcilvODTLnv7JZfDIpuWhsmk40Xi1N26ksScrlwjtiBMoli6yFfZecP50XXXQRZ511Vtoy9fX1VFVVsXnzZtvx7u5utm7dSlVVlWO9qqoqOjs7aW1ttVmTNm3aZNapqqrilVdesdWLrX5LbPeBBx7gnHPO4cEHH7S58JwoKipi1KhRfPDBBynLeL1evN70e2AJwt5kVGU+bwHHHlwPnvgE7HFpeFzJM31VYfzzW6AN44LJ83LuM3HK263l2awzsV51FCGgOq+GMJ8ntWNztyUQBvIZg4sgnXyJpiuz46Qs21YrVgYVlLUVJ11BS8Zto8TDqxXHAO8kFdNx4/w7NPMoDLDHbBkRsfHnmYOY9fZW3N89A9683dZWohhRlnthoNLpGCLdxceaMe+V9T5nCtrPNZeVIOxD5GxJKi8vp6GhIe3D4/HQ3NxMa2srq1atMus+88wzhMNhmpqaHNtubGzE7Xbz9NNPm8fWrFnD+vXraW5uBqC5uZm33nrLJsCWLVtGQUEBY8fGlyrff//9nH322dx///3MnZucQC+RHTt2sHbtWqqrq3O9JYLQbzTXl3LrgsnUlabPP3PFcWOYP20Ik4fE40iyio2JzoVBM+9Nsghp1wKOE/Ao5WVYoJKaYI2jNUEriLv5EuOprZO0z63h9ei2sk7lIkQsUnleHU0DV2JweKY8SQ59pOMzdx0oRZkR+U47cnd82xPD4VnkZYbWgy5A8enIImslQLG5xMffZtdRMHK0bbwQcbfFSA7CVhm/7bVszGdme6mvYVdBauulBHELA40+i0kaM2YMxx57LOeeey6vvPIKL774IosXL+a0006jpqYGgA0bNtDQ0GBahgoLC1m4cCFLlizh2WefZdWqVZx99tk0NzdzyCGR5a/HHHMMY8eO5fTTT+fNN9/kySef5Morr2TRokWmlee+++7jjDPO4Ne//jVNTU20tLTQ0tJCW1ubOb6LL76Y5cuXs27dOlasWMGJJ56IruvMnz+/r26JIPQ+Kv0+XjFGVASZNbYyIX9N9t3oeSV43RprfWOSznVqCfvCRdt1oQjqvpQTo++giawdmRzLAxC2TMIel/NmuCk1ngJd0wh63QkuPdCyVD+J+9FlopQmftBawqDuiJhsLYzU/3TwqJzaAeieUsy/ZwXZNDTfvHcxS1INxzM+fy5l/rKIu802TPt7axWQhlK5udsSzjhR6PdEUw5Ezr/09eF8PK6U96fYY0ZtG+OKVUkYYPRpnqR7772XhoYGjjrqKI477jgOP/xwfve735nnu7q6WLNmDbt27TKP3XjjjRx//PHMmzePGTNmUFVVxZ///GfzvK7rPProo+i6TnNzM9/61rc444wzuPbaa80yv/vd7+ju7mbRokVUV1ebj+9///tmmU8//ZT58+czevRoTjnlFEpLS3nppZcoL7es6hCE/ZisV1kBFA9jW8FoWjwTAIXHpVFZ6IsItITA7Ri+aj+gCM6cidNEq+kuVh/kvBdaYvHM7h9nYlt7xDa5fXZKaVY+t/TB3cryf8P8v26Jq1p+qIfnptXy9sQZZNWhFZdid7HLpoBiPQYZTl3gYJvLL14onjAyscdI7qMMIilHATN9ZBnjBxWa7W6tCbJ6xmBCWYh2QRgo9GnEXElJCffdd1/K80OHDk3K0eLz+bj11lu59dZbU9arq6vjscceS3n+ueeeyzi2Bx54IGMZQdivyd7bBkoxqngGG3eMYQtbqCr0URywCByHtsq+Ukl40kL0eudAanvGbTuJWbxtq+cyDj15Vdx7o1x8UO9hfSCPpi3Rg5orsjN9166kFqz9hVIEptvdg/bfm51excbaAmo0VzSbuEXmOcdk2/t3OhjtI2K4UQ43wWJJcjATxi/D+e7ZY5LAn+Y3tGEYKE1FVy9mL66KvEVZlxWEfQHZ4FYQDlBymdxKfCU0lx9v5uJJrLnZHc+nZK6/Ugo9L5pw0aErpSXm07GQKCRS+gZV0kubeLF00eVWtmBmPHkRkZSBLuV17CrNQRu5uu4gfvmx4PvISraolUhhOWbtxxKTRHLgdqzhVB5HT/RejPB+laJwGSe7SiiYEIlhK7KkT4kTabO+sJ6xpWNZNGkRJb4Sjq8/PqnkD5t+yJIpSyj0FiadE4R9GVl7KQgHKLnEJEFkzzEnlIKNnjoMtRYIR1Z9WU/asKZkTP0brSTfi7Fbo9DvoQNn95cRm/BTxOVAokXKoEP56HL5ozu/pr4Bk7v9rGWn80lHV1dyW4aCrx88CJ5MOpWR2HW5dQ3NC91u3ewxLm6taRPAtpObQ+C2t8qP26UR0t34E0LBavNrmTN0DgC3f+Mb7P6gguLX74DxHoLfvRx9cENCe/E+/C4/3530XQCuPexanKgJ1mR76YKwTyGWJEE4QMkpJimxvO2pSjxA4gvH1W1p8uO4XRrXfnUcxbE8TzmN1eJu02B4l8d2brt/sJl0M1XN4kQV0QMMDA4aXNjjeKrYaHRNiyTkNC1JcRmqVHImcEgWwAYKPehiyElDGHHhNxhREaQ+aiGbWjWVy6ZdRtATScTpc+sUB+KpIvT89Ak6BWF/RkSSIBygZBOoa3XNpLZg2C02aZd5x9xbbn+Suy2xX2tcTS5CwzMv7u5xVeVxws58e4EsmtI1RZrhRZux2cwcCDsKUZUQh+mlFA2dMr9lVViiFzGSOAmICiDDQCX62zK8BwpwB9wMKstH1xTf1so5Y+wZnNZwmuPVOT3/YHIkIe/6sU57uQnC/oe42wRhILMHS6pzraqn8c8lBlbHO0lQGrobSurB5Ufp9sBtJ6+ZSngNkQzf6fSLd/qhvND2NyrWb8dfvZshG7vsY01TN2kADmRKc5TYT2JQd3JPbgr0oZR489gddfGl68JZvkSFZCwm3XLCo2ugafFUEdE3PqA0plVPS9GJdZPeeGP/nlZFy/AitpX4OGVjvkNFQdi/EJEkCAOZbGdsB7KLSbI4c1J41JQClSkRYcAP26M13dFgbpu7LXE1m0ILBi2v4+e6lNvyxZV4EZF8QNvKA2wrDzD+c4dNqx2uu9MDbQUa3XVB6tNfSdYYGOiachA8KumVitZIhTIMM4ZL1zVw++ksHcNWl47B+qRmre9VQ3U+o2oGoX22KfvBp1LQSrGtLJIXq/Ckk+hubSU4fXr27QrCAEPcbYJwgKLnkHEbrJYklTQh261H2E8C3Yu+xQeTK1g72bLpdWJMkqXeRVOWoOfns/2bx/LK8fXpzV4J6s2e4Tk7tuVrPDWjgDXV420r9VJRFM4mF5ARvTfZjcIA7im9wHyeTKQdV2THYLY2Xcry/Lhr0bC4Qa33QFMKVybfYVJX1vLO49fz86n4/vcJTJ6cW9uCMIAQkSQIByi5Jg/UVWrxYTjuMI852XoqK/l3UzWdPosNKEEkWSf2oDeyZUlXw1A+r01066Qfd8brSnG6S3l4LW862zX7MnUnwXL2tliZNG45jGhMUub7HLv2DuU366YqY74PSjM3NI68VranduGaq1s2hVlKEA4wRCQJwkBmL8Yk2RMU2oOE02eohjElY5hUMYkpFXGrQ2JMUh4afjSKXIGsB2cAdFvlgD2o3OkLLrVlJzkgPVWv8cX26dydkZ4y50mKW+acnHMQC9yOpgDQYqvc4sdivZn6KbFPlaPoEV0kCICIJEE4YMk1BUD6wO1UXyXRiV1pnDPhHA4unRg/Y7MkRQTFcOWl1lsSn8idIrcT2w+F7UdSWLwMEtvMxJ4pBQMDzRKTZMqfLOLIEktYRVIsX1WSALNkBk9+q/bAkiSKSTiAkcBtQRjI9HHgtj0FQPy5SphDU44ioQ/DKmjcybmIsokmcmmKfK87ko1aAV323lNZgza5BkfG4HAu/V3sqUhwdrelbM0yiETL3O6g27yumLhVSqFw4aEIAwMX8dxPSiX0k/iGZSJXy5Mg7KeIJUkQDjDyo3FBB9UW5VTPE3WPKRIEE5gTqaYUbl2jutBnPRsn1B2vl2byVTGXks2dFMHr1qkvy4s7vEosYsuw70FmpUPzYxmqIxreqMsrWTblKkdjaQ1yCdzepeXRpTwM1oOgdFaeOIIvBgV5Zc7Q+BjNex2xJHkoxksJhorfrWShmKPQySJwWxAOBMSSJAgHGNd8bRxrWrbTWFecU72G6nyOHFPBu9uL+BL76i6r5aOuNEBeQVQkJW6PkeAaS0kW1gvldvPs7G8yf/dv7ccz2aNStF0dGsxu1xDz9ZCwm3V0ZjVcJ2KB29mIJFPwKZ3/Lv8hN80bh2/90wRHBnm46mHChgGfRsroDgIyUtceuG3vIFfLkAgjQQCxJAnCwKYHrpCigIem+lJcqTZjS4Fb11jQVEdjXUmSFSicKU9SFMNiSbIdTzyQLvA4dsjnoz2vIKlcptVtSYHUnjw8FNDtPi8auh3ZFC6AxuDunv+ONJSB0nLLFg4QUi4C3gJOHHkig4LJ6Qji7rbka0kZuJ1rjFGKZJKCcKAhliRBEPaI9KIk0d3We5akVLFQqQK3U6K5aXUV0615MpfNCedtSZxIVaoqryrpfMpYMktfSWWyFLEpGt6DuoIwsBFLkiAIKckmLtwak5R80v4Vk8qSlLgOLfuJOTEyPAt3WzbtsOcGFMNMAaAlnkjuO0VfJb4SLpl6CVc3X20es1qSrPfX2uykaLyZWzdNS7kNfo9ElSDsP8hfgiAIe4yRbcxLCktSyLJ8PVuNpFLE+2gphEO0VsacTvbS2Re+v+S7vOu3Zp9WWWuTz0eMx7joh47n6grqKA+Um69jMUlJVirL68HFAeZOrKahKuaOzDEmKcX72VDSAODoBhSE/RFxtwmCsEckJjV0OGlihEKOxULoKOICSkW30cgkUpLii5Q9IWRs/7SekKpW0Of8tfm5u4YPQw3AMksb2QVubxo7hSPqauCfnzuPxepKsySTtF6/dVsSgKDXFfe75ZwCwPn389njz+aljS8xpWpK5jYEYT9ARJIgDGD0gsLMhfoYBYRTzrsJJ8IJIik6eXdqXmB30nFrOw4OMfNZOiE0trqAQFer+TprS5KDxcXn0fG5stm3Ld5ENiIp5PFQnu9lyTGjyPcm54+yYuoeh/zcqY1EvbO6Lc+dx1F1R2VRXxD2D8TdJggDkLJvn0/eoYeSf8TMPu3H704WBE6B2ikzbidakrrtIml3yRjaNT/dJAgDS8LEGMOVFx1FbaDSPHbR7DHUFvttIsnmbjMiAirX/V0jsU0JkVIKvK49/8p0kh9hVyRofFxNIUNKAw4l4ui2mCSLJSntJsC5bnCbq6gShP0TEUmCMADxT5pEyRmnozy9vSIrwjVfG8fcidVUmUkhU/OV0eWgLDEytjk1vSXJ0NyElY49eshwnJiDaIxRPoo88WX/w8ryKA167QvcM0zqWjj7tJCOC+mz1AwGKSxJDt0bDtnHU2G620hsO51IylXo5OieE4T9FBFJgiAkUVsS4KTJgx3dWImOr68dNIjDR1VkFfvjGzs20oY/Lr5yC6S2vlCOFhLN9rUWUSQLKEGhMYivoWcQSTEnVqIlKefxkX1MkuHOXuxatyWxNp32Psq2JILQIyQmSRCEPULXFJWFfueTCSImb/p0tPwCvPXDzGNOIiJ5F3scFIjT5J0gHKL/TiHACBah0NC7s83VlF2x3mjIcGX/VWxucJtkpUqQr2WjYN0/khsoqc+6L0E40BGRJAjCnqMclEnicSKr1gKTD7Ydc7SAxKwlmQSGgyUpcXVb/HikbLbuNpWq96y9dSq6Ai1zfZVDzJBpSYpUtDZiLzjiKNA0qBgL/34ifrxyLEy/GAqq0wxd3G2CACKSBEHIEaeYn0xxQDmTFGmdqv3k4055kpSKv9AdcjWlclU5ORurAkOAj1KMx6l+6nuz0++irXBwVh6tigIvm7d1MLIyP9KqUgkpALCLG02HEbNsIzGpnZrVyElsUxAOMEQkCYKwx9hWVtnSdGfhakpjScqIg1suk/VJC2VpSUpqRhF0lTCv/v/By8tT1vMaig5lmJardDFJ/3NcHbVqNpNTlojz06+Ppytk4Pfo0dHYMVSaK5dtSQShR0jgtiAIe0wk+aOjHyljXeel68kpAJI1i2Ogkq2O4bCvipMlKe34gHavzvKDy6n0jsSt0q/4O2VHAXXdburCJzlmBbdmNwpr0eDzLO6TS9dMgQQOMUl9ZfERS5JwACMiSRCEnHC0V9iEib10OgwcpJVhpJ+XleXfDBO4k80oG5EU2xmlwOcm3+fmzq8OZWthZAVaKMOGdhUhF9/YUYBfVSbtSJeKnsiQRBdn2lV0InQEoUeISBIEYc9Jla0xw+TcXDELlIaH4pzqWQpmOJ8sTbSwQ0ySrUXDVHqmDouOpzzfQyjLwG8jFrjtnG0JiGTwBjhsRFlWbdpaUYl75qUtnXvjPa0rCPsRIpIEQehdss/VyCGVs8hTI9FSZNy24Q5EgpFdKdINpCXeXrYxSVZGV+UzqNjPqMp8RlYEs6ozf1pt1NoT6TsQc5VZuh9RHuT7s0YxdWhxcgMZSFzdFhNljlSO7Unr0acikoQDFwncFgRhj1E9tCRBCjuFU3tVE6GjI2HZe/rfednHJKUe57+OqMXn1vG5dTSlKAp4GFdTwOqN25x6NJ/5PJGv1680VGJ8sROPmeDIMhZNUVuc16PVgUnxTunaqJkMMy6FoiE59yOWJOFARkSSIAi9i1WY6N60RSMuIweh4zThK5WcFyhLcWEtlWvg9qcNpUnH3HpmI3ws95Fb16EX9nxLREuIW09rH1MKBjdm37hYjwQBEHebIAg54mj1SDxWPxMOPh0KajK25zS5ZzNFZ2N9cWp77ZgqALYMyU9RKXd3nBPm6BLH2UvtQ/LqtozJN7NG3G2CAGJJEgShF7AvuwcO+U4ulVMey5xxO3VQNDiLpA/HVrB7iMb2Eh/H/vdb2Y8zR1Jl0VYJg+qpsNGSLGmK2pKexGtlQkSScOAiIkkQhH0LTyBL60U2liTn3E1tFYHcx5UjKosNf/eU2NXVlgQ4cmwlx0zMbLnLCrEeCQIg7jZBEHoLI+HfbKoYlpikynFQMgx8Rb0/SfdSc6NLRpvPG6rymTi4ME2X0U4zBJf3dEsXa2oCv0dn9rgqPH0Q+ySCSTiQ6VORtHXrVhYsWEBBQQFFRUUsXLiQHTt2pK3T3t7OokWLKC0tJRgMMm/ePDZt2mQrs379eubOnUsgEKCiooJLLrmE7u5u8/xzzz2HUirp0dLSYmvn1ltvZejQofh8PpqamnjllVd67+IF4QDCPtHnFnNjlnb5wF8SeR5d3bane8L1OPonVrF6EnOGzQHgxBEnMq50nFnE59YZVJTavWUOPcM19NTdFqlnqZtqhWEPW3d+LggHFn0qkhYsWMDq1atZtmwZjz76KM8//zznnXde2joXXnghjzzyCA8++CDLly9n48aNnHTSSeb5UCjE3Llz6ezsZMWKFdx9990sXbqUq666KqmtNWvW8Nlnn5mPiooK89wf//hHlixZwtVXX81rr73GQQcdxOzZs9m8eXPv3QBB2A/pveBgGFqarWvNaSDZ1EuWSY61UuVk1HTm1s/lpiNu4qi6o3oo2vpGZGgq9ca8e4xYjwQB6EOR9O677/LEE0/w+9//nqamJg4//HBuueUWHnjgATZu3OhYp62tjTvvvJMbbriBI488ksbGRu666y5WrFjBSy+9BMBTTz3FO++8wz333MOkSZOYM2cOP/3pT7n11lvp7Oy0tVdRUUFVVZX50Cy/tG644QbOPfdczj77bMaOHcsdd9xBIBDgD3/4Q1/dEkE4MMjBfFMa9DK5roSxgwpsx7NaQedI5sDtnuDSehC+GRtvJkvSHgmSvrIkCYIAfSiSVq5cSVFREVOmTDGPzZo1C03TePnllx3rrFq1iq6uLmbNmmUea2hoYMiQIaxcudJsd8KECVRWVpplZs+ezbZt21i9erWtvUmTJlFdXc3RRx/Niy++aB7v7Oxk1apVtn40TWPWrFlmP050dHSwbds220MQBLt1KVdhkudz404xwae1WmWVAiC+vcjeJ9rruJPsh3tJuWlK9Z0lyZMXf54hpkoQ9mf67NPf0tJic28BuFwuSkpKkmKDrHU8Hg9FRUW245WVlWadlpYWm0CKnY+dA6iuruaOO+7goYce4qGHHqK2tpaZM2fy2muvAfD5558TCoUc20k1NoDrrruOwsJC81FbW5vhLgjC/oeTcLEdy1UE7GXXTm+6C9P2E+umeiIcZQkHSOi+xzFJKt6YYeuwF/AVwqEXwPSLxUIlHNDk/Om//PLLHYOirY/33nuvL8aaNaNHj+b888+nsbGRQw89lD/84Q8ceuih3HjjjXvU7hVXXEFbW5v5+OSTT3ppxIIwcNjTYGqHBnu3PQuOeRszdtfLySQBvJbElb1kSVJK2Te47W2GHga1U/uufUEYAOTsaL/ooos466yz0papr6+nqqoqKQi6u7ubrVu3UlVV5VivqqqKzs5OWltbbdakTZs2mXWqqqqSVqHFVr+lahdg2rRpvPDCCwCUlZWh63rSqjlrP054vV683vTbLAjCgUjAHYjP/blmlE7nUUt/0uFY5pgkzUFYWMspDDzFHrpaO5PKmYyfB28/BJPPhI1fpi6XBXsiOq0Zt3tdvAqCkLslqby8nIaGhrQPj8dDc3Mzra2trFq1yqz7zDPPEA6HaWpqcmy7sbERt9vN008/bR5bs2YN69evp7m5GYDm5mbeeustmwBbtmwZBQUFjB2beqfrN954g+rqagA8Hg+NjY22fsLhME8//bTZjyAI2XPkkCPJVzqDlCf3yn06uTtscJtFkseiyaXkjy2k8sornQtMPAVOvRfKRmQ5jtR97pH7L3rvNMtzQRB6jz7LuD1mzBiOPfZYzj33XO644w66urpYvHgxp512GjU1kaywGzZs4KijjuJ//ud/mDZtGoWFhSxcuJAlS5ZQUlJCQUEB3/ve92hubuaQQw4B4JhjjmHs2LGcfvrp/PKXv6SlpYUrr7ySRYsWmVaem266iWHDhjFu3Dja29v5/e9/zzPPPMNTTz1ljm/JkiWceeaZTJkyhWnTpnHTTTexc+dOzj777L66JYKw3+LVvQxVXjDCObuTUm3fkVwwqWJuHUXRHarVFPmxblCieTSKDiqBwYPSNJT+63NvWHa+fvAgip/14nPrfd6XIByI9Om2JPfeey+LFy/mqKOOQtM05s2bx80332ye7+rqYs2aNezatcs8duONN5plOzo6mD17Nrfddpt5Xtd1Hn30Ub7zne/Q3NxMXl4eZ555Jtdee61ZprOzk4suuogNGzYQCASYOHEif//73zniiCPMMqeeeipbtmzhqquuoqWlhUmTJvHEE08kBXMLgmCn1wOf04mJNDkqvcPqMzbt6G7TFOGEY0Fv73wVKkuPtquyXGNv7d0GcMy4KjakSWgpCMKe0aciqaSkhPvuuy/l+aFDh2IkxC/4fD5uvfVWbr311pT16urqeOyxx1Kev/TSS7n00kszjm/x4sUsXrw4YzlBELIn503uLaun8qYfjithVawTBccfT/5RR2Yei4NMcmmKNNFGe509sjiJi00Q+hTZ4FYQhF6m54HbJQsWZFWl8Pi5ufVhQdMUSaakXsLYS+kFTEQkCUKfIgkwBEHIjUzzci/GJGXrivJVR1xOwckjE4aSnExS30vCwr6Kro/6FJEkCH2KWJIEQRgYpNEDZTMq6d7ZjXuUPdDaMKL1LGJC1xSE0nWTm8orDLhp29WVUx1bf+JuE4R9FrEkCYKQE6mtO4btn2wpnBfZtiP/6KPTlgsefjgA3tGjk8ekKdz57pR1rbGPWWQAyInL5zRwzLhKRlQEUwsslToCfW9lABcEIXfEkiQIQk74Xb27mso3ahSDfnMTmkOiVquVpfDrX8c7ajTekdnmJorLEc1iPXI5qaQ9sMhU5Ps4deoQbn9uLa1ZNLmxcTCDH2+jZXhhj/sUBGHvICJJEIScaK5pZs3WNYwpHWM7nldfwK512wiOzE9RMzVOAikR5XLhnzA+x5YjMklXiv/8xkQ0pbj5zb/lPL7e5POGCj4oDrErP5J4UyxJgrDvIiJJEISccGtuzp14btLxkkMqKJ5SjOptf1Ym5vwnPH6Z4ymjeCi07YJBUygL7t0theyWJPs92VXotZTr+f1SnniGc71QLFOC0NuISBIEoXcwjL0vkACClv0WE5M0lY2EKSdAafYuuj0jc0BWznmk0qCUYtBvboJwGOVOHZMlCELPEJEkCMLAJo0lxkBBZeo9HffSMOwnE8rtqbstG1elIAg9Q1a3CYLQS/SiiSQnLCJDs//uc8q4namJvkZikARh4CAiSRCEfZacBYUnr28G0gM0yWEkCAMeEUmCIPQOvRlsEyVnkeQN2l4m7g2ZXZ97ch3ZjNdeZo+SSQqC0KeISBIEYWBjFRkee/qBrN1tvUaq/pSlhCSTFISBgogkQRBSMr4skpfoiNoj+nkk6bCIjARLUo/a6KUW7Aai1GJNLEmCsO8iq9sEQUjJORPO4bOdnzE4ODiL0v0VuG3B01ORZKVn12EAW1w15utUFiKxHAnCwEFEkiAIKXFpLmrza/t7GOnRdCgZDh1tUJC4wW12gqe9LPcs4U60uUr5Y8l32KXl8d0UZcTdJggDBxFJgiAMbJSCY34GGBHBZCFTTNKL80ZS9WEb7sNGwTt7NowhJQFe/Wgrm92D0hdMcK+JSBKEfRcRSYIg7LOMLB4JgEf3pC+o9Sy8sq0iQFtFgLGePf8qPGZsJWHD4C+vbQDSb0siCMLAQAK3BUHYZyn2FfOzw37GddOv61F9J0vSUUOOAuCg8oP2aGyJuHSN4ydaY5LsI0mFBG4Lwr6LWJIEQdinKfIV9biuU0zS4YMOZ2TxSMr95Xz/2e/vwcjSk6320ZWeuZAgCP2CiCRBEA4olFJU5VXZjlktTqoPkmJmGo8gCPsm4m4TBKF3GDQl8m/xsP4dxz6PiCJBGCiIJUkQhN6h+bvw0T+grrm/R2Ky9zNuWxExJAgDHRFJgiD0Dp48GH1sf4/CRs/2busdxIsmCAMfcbcJgrDfUegtBGBs6dh+G0NZ0NtvfQuC0DuIJUkQhP2Oi6dczFufv0VTddNe7/vnJ06gvStEod+91/sWBKF3EZEkCMJ+R7GvmBmDZ/Sw9p7FMVUV+jKUED+cIAwUxN0mCIIgCILggIgkQRAOeGT/NEEQnBCRJAiCIAiC4ICIJEEQBEEQBAdEJAmCIOxNxLMnCAMGEUmCIAgWVJ9n6RaVJAgDBRFJgiAIFkTCCIIQQ0SSIAgHPPWF9XutrxJv8V7rSxCEPaNPRdLWrVtZsGABBQUFFBUVsXDhQnbs2JG2Tnt7O4sWLaK0tJRgMMi8efPYtGmTrcz69euZO3cugUCAiooKLrnkErq7u83zZ511FkqppMe4cePMMtdcc03S+YaGht69AYIg7NNc1XwVp4w+haPqjtprfc4ffcpe60sQhD2jT0XSggULWL16NcuWLePRRx/l+eef57zzzktb58ILL+SRRx7hwQcfZPny5WzcuJGTTjrJPB8KhZg7dy6dnZ2sWLGCu+++m6VLl3LVVVeZZX7zm9/w2WefmY9PPvmEkpISTj75ZFtf48aNs5V74YUXevcGCIKwT1MRqGDG4Bm4tb23hUiRt4gib9Fe608QhJ7TZ9uSvPvuuzzxxBO8+uqrTJkyBYBbbrmF4447juuvv56ampqkOm1tbdx5553cd999HHnkkQDcddddjBkzhpdeeolDDjmEp556infeeYe///3vVFZWMmnSJH76059y2WWXcc011+DxeCgsLKSwsNBs969//StffvklZ599tv3iXS6qqqr66hYIgiA4YvR5cLggCL1Bn1mSVq5cSVFRkSmQAGbNmoWmabz88suOdVatWkVXVxezZs0yjzU0NDBkyBBWrlxptjthwgQqKyvNMrNnz2bbtm2sXr3asd0777yTWbNmUVdXZzv+/vvvU1NTQ319PQsWLGD9+vVpr6mjo4Nt27bZHoIg7B9MGBz5YVUY6HurUsgI9XkfgiDsOX0mklpaWqioqLAdc7lclJSU0NLSkrKOx+OhqKjIdryystKs09LSYhNIsfOxc4ls3LiRxx9/nHPOOcd2vKmpiaVLl/LEE09w++2389FHHzF9+nS2b9+e8pquu+4600pVWFhIbW1tyrKCIAwszp1ez7cOqWPykKI+7ytshPu8D0EQ9pycRdLll1/uGBRtfbz33nt9MdYecffdd1NUVMQJJ5xgOz5nzhxOPvlkJk6cyOzZs3nsscdobW3lT3/6U8q2rrjiCtra2szHJ5980sejFwRhb5HndXFEQwUevQ9+O/pL4s81F4Yh7jZBGAjkHJN00UUXcdZZZ6UtU19fT1VVFZs3b7Yd7+7uZuvWrSnjgKqqqujs7KS1tdVmTdq0aZNZp6qqildeecVWL7b6LbFdwzD4wx/+wOmnn47H40k75qKiIkaNGsUHH3yQsozX68Xr9aZtRxAEIQmXB076HSgNNE3cbYIwQMhZJJWXl1NeXp6xXHNzM62traxatYrGxkYAnnnmGcLhME1NTY51GhsbcbvdPP3008ybNw+ANWvWsH79epqbm812f/7zn7N582bTnbds2TIKCgoYO3asrb3ly5fzwQcfsHDhwozj3bFjB2vXruX000/PWFYQBCFnfPHFJGJJEoSBQZ/FJI0ZM4Zjjz2Wc889l1deeYUXX3yRxYsXc9ppp5kr2zZs2EBDQ4NpGSosLGThwoUsWbKEZ599llWrVnH22WfT3NzMIYccAsAxxxzD2LFjOf3003nzzTd58sknufLKK1m0aFGSlefOO++kqamJ8ePHJ43v4osvZvny5axbt44VK1Zw4oknous68+fP76tbIgiCAEhMkiAMFPosBQDAvffey+LFiznqqKPQNI158+Zx8803m+e7urpYs2YNu3btMo/deOONZtmOjg5mz57NbbfdZp7XdZ1HH32U73znOzQ3N5OXl8eZZ57Jtddea+u7ra2Nhx56iN/85jeOY/v000+ZP38+X3zxBeXl5Rx++OG89NJLWVnJBEHYj9kLVh4RSYIwMFCG2H17zLZt2ygsLKStrY2CgoL+Ho4gCL3BEz+ErWsjz7/5xz7pYvHTi83n/3XUf/VJH4IgpCbb+Vv2bhMEQdjLSMZtQRgY9Km7TRAEQUhm0aRF/PmDPzN32Nz+HoogCGkQkSQIgrCXqQ5Ws2jSov4ehiAIGRB3myAIgg0J0xQEIYKIJEEQBEEQBAdEJAmCIAiCIDggIkkQBMGKZEURBCGKiCRBEARBEAQHRCQJgiDYEEuSIAgRRCQJgiAIgiA4ICJJEARBEATBARFJgiAIgiAIDohIEgRBEARBcEBEkiAIgiAIggMikgRBEARBEBwQkSQIgmBFkkkKghBFRJIgCIIgCIIDIpIEQRAEQRAcEJEkCIIgCILggIgkQRAEGxKTJAhCBBFJgiAIgiAIDohIEgRBEARBcEBEkiAIgiAIggMikgRBEARBEBwQkSQIgmBFkkkKghBFRJIgCIIV3dPfIxAEYR9BRJIgCIKV5u9CsBKaF/X3SARB6Gdc/T0AQRCEfYrCwfC1m/t7FIIg7AOIJUkQBEEQBMEBEUmCIAiCIAgOiEgSBEEQBEFwQESSIAiCIAiCAyKSBEEQBEEQHBCRJAiCIAiC4ICIJEEQBEEQBAf6TCRt3bqVBQsWUFBQQFFREQsXLmTHjh1p67S3t7No0SJKS0sJBoPMmzePTZs22cpccMEFNDY24vV6mTRpkmM7//rXv5g+fTo+n4/a2lp++ctfJpV58MEHaWhowOfzMWHCBB577LEeX6sgCIIgCPsffSaSFixYwOrVq1m2bBmPPvoozz//POedd17aOhdeeCGPPPIIDz74IMuXL2fjxo2cdNJJSeX+3//7f5x66qmObWzbto1jjjmGuro6Vq1axa9+9SuuueYafve735llVqxYwfz581m4cCGvv/46J5xwAieccAJvv/32nl20IAiCIAj7D0Yf8M477xiA8eqrr5rHHn/8cUMpZWzYsMGxTmtrq+F2u40HH3zQPPbuu+8agLFy5cqk8ldffbVx0EEHJR2/7bbbjOLiYqOjo8M8dtlllxmjR482X59yyinG3LlzbfWampqM888/P+trNAzDaGtrMwCjra0tp3qCIAiCIPQf2c7ffWJJWrlyJUVFRUyZMsU8NmvWLDRN4+WXX3ass2rVKrq6upg1a5Z5rKGhgSFDhrBy5cqc+p4xYwYeT3yTytmzZ7NmzRq+/PJLs4y1n1iZXPoRBEEQBGH/pk/2bmtpaaGiosLekctFSUkJLS0tKet4PB6KiopsxysrK1PWSdXOsGHDktqInSsuLqalpcU8lks/HR0ddHR0mK+3bduW9bgEQRAEQRhY5GRJuvzyy1FKpX289957fTXWfue6666jsLDQfNTW1vb3kARBEARB6CNysiRddNFFnHXWWWnL1NfXU1VVxebNm23Hu7u72bp1K1VVVY71qqqq6OzspLW11WZN2rRpU8o6qdpJXBEXex1rJ1WZTP1cccUVLFmyxHy9bds2EUqCIAiCsJ+Sk0gqLy+nvLw8Y7nm5mZaW1tZtWoVjY2NADzzzDOEw2Gampoc6zQ2NuJ2u3n66aeZN28eAGvWrGH9+vU0NzdnPcbm5mZ+9KMf0dXVhdvtBmDZsmWMHj2a4uJis8zTTz/ND37wA7PesmXLMvbj9Xrxer3ma8MwAHG7CYIgCMJAIjZvx+bxlPRV5Pixxx5rHHzwwcbLL79svPDCC8bIkSON+fPnm+c//fRTY/To0cbLL79sHvv2t79tDBkyxHjmmWeMf/7zn0Zzc7PR3Nxsa/f99983Xn/9deP88883Ro0aZbz++uvG66+/bq5ma21tNSorK43TTz/dePvtt40HHnjACAQCxm9/+1uzjRdffNFwuVzG9ddfb7z77rvG1VdfbbjdbuOtt97K6Ro/+eQTA5CHPOQhD3nIQx4D8PHJJ5+kneeVYWSSUT1j69atLF68mEceeQRN05g3bx4333wzwWAQgHXr1jFs2DCeffZZZs6cCUSSSV500UXcf//9dHR0MHv2bG677TabG2zmzJksX748qb+PPvqIoUOHApFkkosWLeLVV1+lrKyM733ve1x22WW28g8++CBXXnkl69atY+TIkfzyl7/kuOOOy+kaw+EwGzduJD8/H6VUTnX3N2Kux08++YSCgoL+Hs5+h9zfvkPubd8i97fvkHvbcwzDYPv27dTU1KBpqcOz+0wkCQcW27Zto7CwkLa2Nvlj7QPk/vYdcm/7Frm/fYfc275H9m4TBEEQBEFwQESSIAiCIAiCAyKShF7B6/Vy9dVX21b/Cb2H3N++Q+5t3yL3t++Qe9v3SEySIAiCIAiCA2JJEgRBEARBcEBEkiAIgiAIggMikgRBEARBEBwQkSQIgiAIguCAiCShz+jo6GDSpEkopXjjjTf6ezj7BevWrWPhwoUMGzYMv9/P8OHDufrqq+ns7OzvoQ1Ybr31VoYOHYrP56OpqYlXXnmlv4c04LnuuuuYOnUq+fn5VFRUcMIJJ7BmzZr+HtZ+yS9+8QuUUra9SIXeQ0SS0Gdceuml1NTU9Pcw9ivee+89wuEwv/3tb1m9ejU33ngjd9xxBz/84Q/7e2gDkj/+8Y8sWbKEq6++mtdee42DDjqI2bNns3nz5v4e2oBm+fLlLFq0iJdeeolly5bR1dXFMcccw86dO/t7aPsVr776Kr/97W+ZOHFifw9lv0VSAAh9wuOPP86SJUt46KGHGDduHK+//jqTJk3q72Htl/zqV7/i9ttv58MPP+zvoQw4mpqamDp1Kv/1X/8FRPZjrK2t5Xvf+x6XX355P49u/2HLli1UVFSwfPlyZsyY0d/D2S/YsWMHkydP5rbbbuNnP/sZkyZN4qabburvYe13iCVJ6HU2bdrEueeey//+7/8SCAT6ezj7PW1tbZSUlPT3MAYcnZ2drFq1ilmzZpnHNE1j1qxZrFy5sh9Htv/R1tYGIJ/TXmTRokXMnTvX9vkVeh9Xfw9A2L8wDIOzzjqLb3/720yZMoV169b195D2az744ANuueUWrr/++v4eyoDj888/JxQKUVlZaTteWVnJe++910+j2v8Ih8P84Ac/4LDDDmP8+PH9PZz9ggceeIDXXnuNV199tb+Hst8jliQhKy6//HKUUmkf7733Hrfccgvbt2/niiuu6O8hDyiyvb9WNmzYwLHHHsvJJ5/Mueee208jF4T0LFq0iLfffpsHHnigv4eyX/DJJ5/w/e9/n3vvvRefz9ffw9nvkZgkISu2bNnCF198kbZMfX09p5xyCo888ghKKfN4KBRC13UWLFjA3Xff3ddDHZBke389Hg8AGzduZObMmRxyyCEsXboUTZPfO7nS2dlJIBDg//7v/zjhhBPM42eeeSatra08/PDD/Te4/YTFixfz8MMP8/zzzzNs2LD+Hs5+wV//+ldOPPFEdF03j4VCIZRSaJpGR0eH7ZywZ4hIEnqV9evXs23bNvP1xo0bmT17Nv/3f/9HU1MTgwcP7sfR7R9s2LCBI444gsbGRu655x75QtwDmpqamDZtGrfccgsQcQ0NGTKExYsXS+D2HmAYBt/73vf4y1/+wnPPPcfIkSP7e0j7Ddu3b+fjjz+2HTv77LNpaGjgsssuE5dmLyMxSUKvMmTIENvrYDAIwPDhw0Ug9QIbNmxg5syZ1NXVcf3117NlyxbzXFVVVT+ObGCyZMkSzjzzTKZMmcK0adO46aab2LlzJ2effXZ/D21As2jRIu677z4efvhh8vPzaWlpAaCwsBC/39/PoxvY5OfnJwmhvLw8SktLRSD1ASKSBGEAsWzZMj744AM++OCDJNEpRuHcOfXUU9myZQtXXXUVLS0tTJo0iSeeeCIpmFvIjdtvvx2AmTNn2o7fddddnHXWWXt/QILQQ8TdJgiCIAiC4IBEewqCIAiCIDggIkkQBEEQBMEBEUmCIAiCIAgOiEgSBEEQBEFwQESSIAiCIAiCAyKSBEEQBEEQHBCRJAiCIAiC4ICIJEEQeszMmTP5wQ9+YL4eOnQoN910U7+Np6+45pprmDRpUp+1v3TpUoqKivqsfUEQeoYkkxQEocfMnDmTSZMmmcJoy5Yt5OXlEQgEMtYdOnQoP/jBD2wia19lx44ddHR0UFpa2ift7969m+3bt1NRUdEn7QuC0DNkWxJBEHqN8vLy/h5CnxAMBs19CPsCv98ve5oJwj6IuNsEQciKnTt3csYZZxAMBqmurubXv/51Uhmru80wDK655hqGDBmC1+ulpqaGCy64AIhYoD7++GMuvPBClFIopQD44osvmD9/PoMGDSIQCDBhwgTuv/9+Wx8zZ87kggsu4NJLL6WkpISqqiquueYaW5nW1lbOP/98Kisr8fl8jB8/nkcffdQ8/8ILLzB9+nT8fj+1tbVccMEF7Ny5M+W1J7rbzjrrLE444QSuv/56qqurKS0tZdGiRXR1daVs48033+SII44gPz+fgoICGhsb+ec//wk4u9t+9rOfUVFRQX5+Pueccw6XX3654xj+4z/+g8rKSoqKirj22mvp7u7mkksuoaSkhMGDB3PXXXfZ2r3ssssYNWoUgUCA+vp6fvzjH6cdtyAcyIhIEgQhKy655BKWL1/Oww8/zFNPPcVzzz3Ha6+9lrL8Qw89xI033shvf/tb3n//ff76178yYcIEAP785z8zePBgrr32Wj777DM+++wzANrb22lsbORvf/sbb7/9Nueddx6nn346r7zyiq3tu+++m7y8PF5++WV++ctfcu2117Js2TIAwuEwc+bM4cUXX+See+7hnXfe4Re/+AW6rgOwdu1ajj32WObNm8e//vUv/vjHP/LCCy+wePHinO7Hs88+y9q1a3n22We5++67Wbp0KUuXLk1ZfsGCBQwePJhXX32VVatWcfnll+N2ux3L3nvvvfz85z/nP//zP1m1ahVDhgwxN4218swzz7Bx40aef/55brjhBq6++mqOP/54iouLefnll/n2t7/N+eefz6effmrWyc/PZ+nSpbzzzjv85je/4b//+7+58cYbc7p2QThgMARBEDKwfft2w+PxGH/605/MY1988YXh9/uN73//++axuro648YbbzQMwzB+/etfG6NGjTI6Ozsd27SWTcfcuXONiy66yHz9la98xTj88MNtZaZOnWpcdtllhmEYxpNPPmlommasWbPGsb2FCxca5513nu3YP/7xD0PTNGP37t2Oda6++mrjoIMOMl+feeaZRl1dndHd3W0eO/nkk41TTz015XXk5+cbS5cudTx31113GYWFhebrpqYmY9GiRbYyhx12mOMYQqGQeWz06NHG9OnTzdfd3d1GXl6ecf/996cc169+9SujsbEx5XlBOJARS5IgCBlZu3YtnZ2dNDU1mcdKSkoYPXp0yjonn3wyu3fvpr6+nnPPPZe//OUvdHd3p+0nFArx05/+lAkTJlBSUkIwGOTJJ59k/fr1tnITJ060va6urmbz5s0AvPHGGwwePJhRo0Y59vHmm2+ydOlSM84oGAwye/ZswuEwH330UdrxWRk3bpxpnUocgxNLlizhnHPOYdasWfziF79g7dq1KcuuWbOGadOm2Y4lvo6NQdPiX+OVlZWmtQ5A13VKS0tt4/rjH//IYYcdRlVVFcFgkCuvvDLp/gqCEEFEkiAIfUJtbS1r1qzhtttuw+/3893vfpcZM2akjX/51a9+xW9+8xsuu+wynn32Wd544w1mz55NZ2enrVyim0opRTgcBsgYAL1jxw7OP/983njjDfPx5ptv8v777zN8+PCsry/dGJy45pprWL16NXPnzuWZZ55h7Nix/OUvf8m6v2zHkG5cK1euZMGCBRx33HE8+uijvP766/zoRz9Kur+CIEQQkSQIQkaGDx+O2+3m5ZdfNo99+eWX/Pvf/05bz+/389WvfpWbb76Z5557jpUrV/LWW28B4PF4CIVCtvIvvvgiX//61/nWt77FQQcdRH19fcY+Epk4cSKffvppynqTJ0/mnXfeYcSIEUkPj8eTU1+5MmrUKC688EKeeuopTjrppKSg6hijR4/m1VdftR1LfN0TVqxYQV1dHT/60Y+YMmUKI0eO5OOPP97jdgVhf0VEkiAIGQkGgyxcuJBLLrmEZ555hrfffpuzzjrL5upJZOnSpdx55528/fbbfPjhh9xzzz34/X7q6uqAyEq4559/ng0bNvD5558DMHLkSJYtW8aKFSt49913Of/889m0aVNOY/3KV77CjBkzmDdvHsuWLeOjjz7i8ccf54knngAiq7tWrFjB4sWLeeONN3j//fd5+OGHcw7czoXdu3ezePFinnvuOT7++GNefPFFXn31VcaMGeNY/nvf+x533nknd999N++//z4/+9nP+Ne//mWuAuwpI0eOZP369TzwwAOsXbuWm2++eY+tWYKwPyMiSRCErPjVr37F9OnT+epXv8qsWbM4/PDDaWxsTFm+qKiI//7v/+awww5j4sSJ/P3vf+eRRx4xEzJee+21rFu3juHDh5v5la688komT57M7NmzmTlzJlVVVZxwwgk5j/Whhx5i6tSpzJ8/n7Fjx3LppZeaVquJEyeyfPly/v3vfzN9+nQOPvhgrrrqKmpqanK/KVmi6zpffPEFZ5xxBqNGjeKUU05hzpw5/OQnP3Esv2DBAq644gouvvhiJk+ezEcffcRZZ52Fz+fbo3F87Wtf48ILL2Tx4sVMmjSJFStW8OMf/3iP2hSE/RnJuC0IgjAAOProo6mqquJ///d/+3sognDAIBm3BUEQ9jF27drFHXfcwezZs9F1nfvvv5+///3vZi4oQRD2DmJJEgRB2MfYvXs3X/3qV3n99ddpb29n9OjRXHnllZx00kn9PTRBOKAQkSQIgiAIguCABG4LgiAIgiA4ICJJEARBEATBARFJgiAIgiAIDohIEgRBEARBcEBEkiAIgiAIggMikgRBEARBEBwQkSQIgiAIguCAiCRBEARBEAQHRCQJgiAIgiA48P8Dj+5HgGIDptwAAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Distance to normal distribution:\n", "def normal(x, mu, sigma):\n", " delta = x-mu\n", " sigma2 = sigma*sigma\n", " return numpy.exp(-delta*delta/(2*sigma2))/numpy.sqrt(2*numpy.pi*sigma2)\n", "\n", "alpha = 0.7\n", "\n", "fig, ax = subplots()\n", "x_ref = (ref[1][1:]+ref[1][:-1])/2.0\n", "y_ref = ref[0]/ref[0].sum()*npt/(ref[1][-1]-ref[1][0])\n", "ax.plot(x_ref, y_ref-normal(x_ref,0, 1), \n", " \"-\", alpha=alpha, label=\"numpy\")\n", "\n", "x_obt = (obt[1][1:]+obt[1][:-1])/2.0\n", "y_obt = obt[0]/obt[0].sum()*npt/(obt[1][-1]-obt[1][0])\n", "ax.plot(x_obt, y_obt-normal(x_obt, 0, 1), \n", " \"-\", alpha=alpha, label=\"cython Box-Muller\")\n", "\n", "x_obt2 = (obt2[1][1:]+obt2[1][:-1])/2.0 \n", "y_obt2 = obt2[0]/obt2[0].sum()*npt/(obt2[1][-1]-obt2[1][0])\n", "ax.plot(x_obt2, y_obt2-normal(x_obt2, 0, 1), \n", " \"-\", alpha=alpha, label=\"cython Marsaglia\")\n", "\n", "x_cpp = (cpp[1][1:]+cpp[1][:-1])/2.0\n", "y_cpp = cpp[0]/cpp[0].sum()*npt/(cpp[1][-1]-cpp[1][0])\n", "ax.plot(x_cpp, y_cpp-normal(x_cpp, 0, 1), \n", " \"-\", alpha=alpha, label=\"C++\")\n", "ax.set_title(\"Distance to normal distribution\")\n", "ax.set_xlabel(\"distance in sigma\")\n", "ax.legend();" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Total execution time: 116.611\n" ] } ], "source": [ "print(f\"Total execution time: {time.perf_counter()-start_time:.3f}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There was still a lot of time wasted in converting the uniform distribution to the normal one. This was related to the numerous transcendental functions like `sin`, `cos`, `sqrt` and `log` called in the conversion. The Marsaglia algorithm allows to generate 2 values for 2 polls, and does not use `sin` nor `cos` functions, which actually makes it twice faster than the Box-Muller algorithm. And since we generate billions of random values, this matters!\n", "\n", "## Outlook\n", "I created a pool of thread in Python (from multiprocessing) and called the processing of each image in a separated thread. Since the complete generation of the image is in a `nogil` section, this runs efficiently on all cores of the computer. I got a 10x speed-up with multithreading versus the single threaded version on a 8-core computer thanks to hyperthreading. The perfromances are not that fancy but the bottleneck is now somewhere else.\n", "\n", "\n", "## Conclusions\n", "\n", "There are several conclusion one can draw from this:\n", "* Python is not slow !\n", "* Numpy is even fast !\n", "* Re-writting performance critical in C is not a silver bullet\n", "* Re-writting performance critical in C++ does not help either in some cases\n", "* Algorithms matters more than the programming language used !\n", "* Benchmark, profile, measure ... is more deterministic than listening to friends" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.12.3" } }, "nbformat": 4, "nbformat_minor": 4 }