Skip to content

Instantly share code, notes, and snippets.

@brandon-b-miller
Created January 19, 2021 16:09
Show Gist options
  • Save brandon-b-miller/b976111113bf1718f6600e049cd14f6e to your computer and use it in GitHub Desktop.
Save brandon-b-miller/b976111113bf1718f6600e049cd14f6e to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# More performance for less with cuDF Scalars"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This notebook should provide an example of a realistic scenario where a cuDF Scalar can vastly improve the walltime of an iterative process without having to change much code at all. The setup is a fairly simplistic optimizatiom problem minimizing a \"parabola\" in one million dimensions, using basic out of the box no frills gradient descent. \n",
"\n",
"We'll run the optimization algorithm twice, once using standard Python scalars, and once using cuDF scalars. Then, we'll compare the runtime and quantify the difference in performance."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import cudf\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For the published version of this blog, a single Tesla V100 GPU is used - but similar gains should be achievable on other hardware as well."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Tue Jan 19 08:05:48 2021 \n",
"+-----------------------------------------------------------------------------+\n",
"| NVIDIA-SMI 440.64.00 Driver Version: 440.64.00 CUDA Version: 10.2 |\n",
"|-------------------------------+----------------------+----------------------+\n",
"| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |\n",
"| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |\n",
"|===============================+======================+======================|\n",
"| 0 Tesla V100-SXM2... On | 00000000:06:00.0 Off | 0 |\n",
"| N/A 30C P0 44W / 300W | 0MiB / 32510MiB | 0% Default |\n",
"+-------------------------------+----------------------+----------------------+\n",
"| 1 Tesla V100-SXM2... On | 00000000:07:00.0 Off | 0 |\n",
"| N/A 30C P0 42W / 300W | 0MiB / 32510MiB | 0% Default |\n",
"+-------------------------------+----------------------+----------------------+\n",
"| 2 Tesla V100-SXM2... On | 00000000:0A:00.0 Off | 0 |\n",
"| N/A 29C P0 41W / 300W | 0MiB / 32510MiB | 0% Default |\n",
"+-------------------------------+----------------------+----------------------+\n",
"| 3 Tesla V100-SXM2... On | 00000000:0B:00.0 Off | 0 |\n",
"| N/A 28C P0 43W / 300W | 0MiB / 32510MiB | 0% Default |\n",
"+-------------------------------+----------------------+----------------------+\n",
"| 4 Tesla V100-SXM2... On | 00000000:85:00.0 Off | 0 |\n",
"| N/A 28C P0 41W / 300W | 0MiB / 32510MiB | 0% Default |\n",
"+-------------------------------+----------------------+----------------------+\n",
"| 5 Tesla V100-SXM2... On | 00000000:86:00.0 Off | 0 |\n",
"| N/A 30C P0 41W / 300W | 0MiB / 32510MiB | 0% Default |\n",
"+-------------------------------+----------------------+----------------------+\n",
"| 6 Tesla V100-SXM2... On | 00000000:89:00.0 Off | 0 |\n",
"| N/A 33C P0 40W / 300W | 0MiB / 32510MiB | 0% Default |\n",
"+-------------------------------+----------------------+----------------------+\n",
"| 7 Tesla V100-SXM2... On | 00000000:8A:00.0 Off | 0 |\n",
"| N/A 30C P0 45W / 300W | 12MiB / 32510MiB | 0% Default |\n",
"+-------------------------------+----------------------+----------------------+\n",
" \n",
"+-----------------------------------------------------------------------------+\n",
"| Processes: GPU Memory |\n",
"| GPU PID Type Process name Usage |\n",
"|=============================================================================|\n",
"| No running processes found |\n",
"+-----------------------------------------------------------------------------+\n"
]
}
],
"source": [
"!nvidia-smi"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For this example, we're using a very high dimensional parabola. Mathematically, this works the exact same way as a parabola in one dimension, instead of $f(x) = x^2$, we just have $f(\\vec{x}) = x_{0}^2 + x_{1}^2...x_{n}^2$ where $n$ is the number of dimensions (we'll set this to be 1 million for this example).\n",
"\n",
"The fundamental equation of gradient descent is: $\\hspace{5mm} {\\vec{\\theta_{i+1}} = \\vec{\\theta_i} - \\alpha \\nabla \\vec{\\theta_{i}}}$. This equation involves taking a derivative of the function at a point - and since $\\frac{d}{dx} x^2 = 2x$, and the derivative is a linear operator, the gradient of the whole function is just $\\frac{d}{dx} f(\\vec{x}) = 2\\vec{x}$, which is just a constant (2) times the original vector. Multiply that by an adjustable learning rate parameter ($\\alpha$) and that's basically the equation for how to update the parameters.\n",
" \n",
"In plain English this basically says “after we take our step, the place we end up is equal to the place we started plus a step in the direction of steepest descent. We'll keep iterating like this until some condition we choose is fulfilled, and then to take the final values $x_f$ as the global “minimum”. As an easy example let’s start with the vector $\\vec{x} = \\{1, 1, 1 .... 1\\}$. \n",
"\n",
"Below is some simple code to perform this process. It simply performs this update step a set number of times ($n_{iter}), starting from a point in space (input_params), accepts a learning rate (lr) and contains a switch to promote the constant to a cuDF scalar. Let's run this function twice and time it - once with the cuDF scalar off, and once with it on:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"DIM = int(1e6)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"def do_descent(n_iter, lr, input_params, use_cudf_scalar=False):\n",
" output_params = input_params\n",
"\n",
" constant = lr * 2\n",
" if use_cudf_scalar:\n",
" constant = cudf.Scalar(constant)\n",
" \n",
" for i in range(n_iter):\n",
" output_params = output_params - (constant * output_params)\n",
"\n",
" return output_params"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"# A long vector of ones\n",
"initial_params = cudf.Series(np.ones(DIM))"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"# Run the function once to test it\n",
"params = do_descent(1000, 0.001, initial_params)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0 0.135065\n",
"1 0.135065\n",
"2 0.135065\n",
"3 0.135065\n",
"4 0.135065\n",
" ... \n",
"999995 0.135065\n",
"999996 0.135065\n",
"999997 0.135065\n",
"999998 0.135065\n",
"999999 0.135065\n",
"Length: 1000000, dtype: float64"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"params"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2.22 s ± 105 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
]
}
],
"source": [
"# Python Scalar\n",
"no_scalar = %timeit -o do_descent(1000, 0.001, initial_params)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1.66 s ± 50.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
]
}
],
"source": [
"# cudf.Scalar\n",
"yes_scalar = %timeit -o do_descent(1000, 0.001, initial_params, use_cudf_scalar=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Results"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's plot the above results and inspect the performance improvement from using the cuDF scalar."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"text/plain": [
"2.218248599142368"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"no_scalar.average"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1.6562571938016586"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"yes_scalar.average"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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HR10dnbW9sTAfPPNN9988y2eemfpJEmSpH7T0dFR6xMD880333zzzbd46pmlkyRJkvpV3U8MzDfffPPNN9/iqXuWTpIkSep3dT8xMN98880333yLp5eydJIkSdKAUPcTA/PNN9988823eFqSpZMkSZIGjLqfGJhvvvnmm2++xdOLLJ0kSZI0oNT9xMB8880333zzLZ4Klk6SJEkacOp+YmC++eabb775Fk+WTpIkSRqg6n5iYL755ptvvvlDvXiydJIkSdKAVfcTA/PNN998880fysWTpZMkSZIGtLqfGJhvvvnmm2/+UC2eLJ0kSZI04NX9xMB8880333zzh2LxZOkkSZKkWqj7iYH55ptvvvnmD7XiydJJkiRJtVH3EwPzzTfffPPNnzNnTuX5A5WlkyRJkmplMJ14mG+++eabP/Typ06dOmSKJ0snSZIk1c5gOfEw33zzzTd/6OV3dnYydepU5s2bV3n+QGPpJEmSpFoaDCce5ptvvvnmD738jo4OOjs7mT59euXZA42lkyRJkmqr7ice5ptvvvnmD838jo4Oxo0bV3nuQGPpJEmSpFqr+4mH+eabb775QzN/1KhRlWcONJZOkiRJqr26n3iYb7755ps/NPMHO0snSZIk9bl2LJ5a9xMP880333zzh2b+YGbpJEmSpD43ffr0ttwuuu4nHuabb7755g/N/MHK0kmSJEl9bty4cUydOtXiyXzzzTfffPMHMUsnSZIk9blRo0bR2dnJ1KlTa3liYL755ptvvvkWT0tn6SRJkqR+0dHRQWdnZ21PDMw333zzzTff4ql3lk6SJEnqNx0dHbU+MTDffPPNN998i6eeWTpJkiSpX9X9xMB8880333zzLZ6616elU0S8JyLOiYiHImJ2RNwbEd+IiJVbOLYjIr4TEY+Xx94YETv1xbglSZLUXnU/MTDffPPNN998i6eX6uuZTkcCC4H/BPYA/gf4BHBFRCxtLP8LHAocA+wNPA5cFhGvb9toJUmS1GfqfmJgvvnmm2+++RZPS+rr0ukdmfm+zDw9M6/NzB8ChwPbAxN7OigitgQOAD6bmb/IzD8C7wMeBo5r/7AlSZLUF+p+YmC++eabb775Fk8v6tPSKTOndbP55vL9q3o59J3AfOCspqwFwJnA7hGxQmWDlCRJUr+q+4mB+eabb7755ls8FQbCQuI7l+//0ctzJgCTM/OFLtvvBkYBG7djYJIkSeofdT8xMN98880333yLp34unSLiVRSXx12Zmbf08tTVgOndbH+maX9Pr3FYRNwSEbdMm9bdRCtJkiQNRHU/MTDffPPNN9/8oV489VvpFBFjgPOBBcBH2vU6mXliZm6Tmdusueaa7XoZSZIktUHdTwzMN9988803fygXT/1SOkXEaOBCYENg98x8ZCmHTAfGdbO9McPpmW72SZIkaRCo+4mB+eabb7755g/V4qnPS6eIGAn8DtgG2Csz72zhsLuBDSJixS7bNwfmAfdXO0pJkiQNJHU/MTDffPPNN9/8oVg8jejLF4uIYcDpwC7A3pl5U4uHXgh8FXgvcEqZNQJ4P3B5Zs5tw3AlSZI0gDT/4j5hwgTGjh1rvvn9lr/KKquwxx57sOGGGxIRPPjgg5x11llcf/31S82fNGlSt9t/9rOf8cQTTyyxbeWVV2aXXXZhk002oaOjg2nTpnH33Xfz5z//GYAxY8aw/fbbs9FGG7HaaquxcOFCnnzySa699loeeuihlj+fun39e8p/6qmn+OEPf8h5553H3//+98rz6/71MX9g5A8lfT3T6b8piqPvAc9HxBub3tYBiIj1I2JBRBzTOCgzbwPOAn4YER+LiF2BM4ENgGP7+HOQJElSP6n7X6TNHxz5I0eO5KCDDmKNNdbgvPPO4/e//z2rrbYahx9+OFtttVVL+bfddhu//OUvl3h7+umnXzLeQw89lNVXX51LLrmEX//611x11VU88cQTi/PXXnttXvva1/LPf/6T3/72t5x33nksWLCAgw8+mE033XSZPq+6fP17y298zi+80PXG59Xk1/3rY/7AyJ8zZ07l+QNVX5dOe5bvvwTc2OXtY+W+AIZ3M7aPACcBXwMuAtYF9sjMv7V5zJIkSRpABtOJh/n1zN96660ZN24cZ555Jv/85z+59957OeOMMxg7diy77rprS/mzZs3ikUceWeJt/vz5Szxn7733ZtasWZx88sncfffdPPTQQ9x3333ccssti/MffvhhTjjhBP70pz/x4IMPct9993HmmWfy9NNPs8MOOyzz51aHr39vVlllFQAeffTRWo7f/KGRP3Xq1CFTPPXp5XWZOb6F50yhKJ66bp8NfK58kyRJ0hA22C61ML9e+ZttthmPPPIIzzzz4v2MGgXQZpttxo033viyxz9u3Dg23nhjfv/737No0aIex9943GzRokU88cQTrL322kt9nY022oiJEyey5pprMmzYMGbOnMmddxbL7jbGv9lmm7HLLruw3nrrMWLECB5//HGuvPJKHn744SWy1l9/fXbaaSde9apXMWzYMJ555hn+8pe/cNtttwEwbNgwJk6cyOte9zrGjBnDU089xR133MFNN920+HMcO3YsRxxxBBdeeCGrrLIKW2+9NSNHjuShhx7ioosuYubMmYtfb+TIkey2225MmDCBESNGMHnyZK6//noAXvWqV3H55ZczYcIENt98c3bddVfWWmstRo4cyXPPPcf999/PRRddtAzfke6//gPx59P8gZ/f2dnJ1KlT2XDDDSvNHoj65e51kiRJ0ss1WP7ibX798hsnjF1NmzaNNddcs6X8bbbZhi9/+ct86Utf4qCDDmK99dZbYn/j8YIFC/jQhz7El7/8ZY466ij23XdfRo8e3Wv+8OHDWXfddXnqqad6/TzGjRvH/vvvz/Tp0/nd737HGWecwY033sjIkSMX58+dO5dDDjmE0aNHc+GFF/Lb3/6W2bNn8+EPf5i11lprcdZmm23GQQcdxPDhw/nDH/7AmWeeyW233bbECfu+++7Lm9/8Zm6//XbOOOMM/v73v7PLLrvw9re//SVj23HHHVlttdU4//zzueSSS1h33XXZb7/9lnjO3nvvzdZbb82NN97IWWedxVNPPcW73/1uAFZccUUmTJjAAw88wIEHHkhmct5553H66adz7bXXMmzYyzsVHsg/n+YP/PyOjg46OzuZPn165dkDjaWTJEmSaqvuJx7m1zN/9OjR3V4aM3v2bEaPHr3U/Ntvv52LLrqIU089lQsvvJDRo0dz0EEHMX78+MXPWXnllQHYZ599ePrppzn99NO58sor2WSTTfjQhz5ERPSYP3HiRFZZZZXFi433ZK211mLEiBFcdNFF3H///UyePJlbb72VK6+8cvH4P/vZz/Lkk0/yox/9iHvuuWfx5XvTp09n5513Xpy155578sQTT3DKKadw11138eCDD/KXv/yFq6++GiiKui222II//elPXH311TzwwAPcdNNNXHbZZbzhDW9Y4usGxcyxc845h/vvv5/bb7+dP//5z4wfP37x12X11Vdniy224Oqrr+a6667jgQce4IorruBf//rXEl//iRMnstJKK3HOOefwr3/9iylTpvD3v/+dCy+8sNevTSsG6s+n+fXI7+joYNy4cZXnDjSWTpIkSaq1up94mD/08s8991zuvvtuHn74Ye644w5+9atfMWvWLHbZZZfFx0UUK45MmTKFiy++eHEhdNFFF7H22muz0UYbdZu/xRZb8OY3v5lrr732JZe/dfXEE0+wcOFC3vOe97D55puz0korLbF/xIgRbLzxxvzjH//grrvuYubMmQwbNmzx3frWX399ANZYYw3Gjh3L3/72NzKz29dqPPeOO+5YYvt9990HwEorrbTE17+xveHJJ58EYNVVVwVgnXXWYdiwYYsvMWy46667lng8b948XnjhBd7xjnew0UYbLV7zqSqD8efT/L7LHzVqVOWZA42lkyRJkmqv7ice5tcrf/bs2XR0dLxk++jRo5k9e/Yy58+bN4/77rtviTWYGndfe+CBB5Z4buNx86VtjfyVV16ZffbZh7/97W9cc801S/08nnnmGX79618TEey7774ceeSRfOxjH1tcEI0ePZphw4ax++67c9lll/H973+fY445hmOOOYbtt9+e0aNHExGLZyk1r7fU3dcGigXUmz333HNAsbbU3XffvTij69dx4cKFQFGEAYwZM2aJ4xuef/75JR7PnTuXU089lfnz57Pffvvxuc99jk9+8pO85jWvWerXp1UD7efT/HrlD3Z9upC4JEmSBMVJdtWaTwzquris+fXInzZtGp2dnS/ZvuaaazJt2rSXnd94jd50nVG01VZbccABB/DnP/+Z888/f/GMoKWZMmUKU6ZMYfjw4ay33nq85S1v4cADD+SHP/whc+bMYdGiRdx8883cfvvtzJo1iwcffJANN9xw8WVumbm4IGts606jRBozZswS69g0yqPMZMKECUtcHtebRtnUNa/rbC0oZnSde+65zJw5kwULFnDooYfy3ve+l5/97Gfdrs21PAbSz6f59csfzJzpJEmSpD43ffr0ttwuuu5/8Ta/Hvn33nsv66yzzhLrsYwdO5Z1112Xe++9d5nzV1hhBTbddFMeffTRxdseeeQRZs2axcYbb7xERuNx83PXWWcd9t9/fyZPnswFF1zAXXfdtcxfn4ULFy6++9uoUaMYO3Ys8+fP5+GHH+YVr3gFjz/+OLNmzWLkyJFceuml3HPPPTz22GMAPP3000yfPp2tt966x/yHHnoIgNe+9rVLbN9iiy2AovwaO3Ysm266KfDiTK+ePPLIIyxatIgJEyYssb1rfrNVVlmFYcOGccIJJzBs2DDWWGONXl9jWQ2Un0/z65k/WFk6SZIkqc+NGzeOqVOnWjyZX8v8W2+9lRkzZvCBD3yAzTbbjM0224wPfOADzJw5k1tvvXXx81ZddVWOOeaYJRbc3muvvTjuuOPo7OxkjTXWYMstt+SQQw5hzJgx/PGPf1z8vEWLFnHllVey6aabsvfee7PRRhux7bbb8va3v53JkyczefJkoFhP6cADD+SFF17ghhtuYPPNN2evvfZi0aJFS53ttM022/Dud7+b173uday//vq85jWvYeedd2bmzJmLZwBddtllrL322nzwgx/kta99LVtuuSX7778/2223HW9+85sXZ1166aWstdZaHHTQQUyYMIENNtiAbbfdlokTJwIwdepU7rzzTiZOnMjOO+/MhhtuyM4778zEiRO58847F79eY82lxx57rNfv79NPP82dd97JW97yFnbccUc23HBD3va2t7HJJpss8bxNN92U/fffn6222orx48ez3Xbb8bnPfY7nn3/+JetBVWEg/HyaX9/8wcjL6yRJktTnRo0atfi28zNmzKjdpRDmD+38+fPnc8opp7D77ruz3377ATB58mQuvfTSJS4djYjFC283PPXUU7z61a/m8MMPp6Ojg7lz5/LII49wwQUXLDF7CYq73GUmb37zm3n961/P7NmzueOOO5Yop9ZZZx1Gjx7N6NGjOfjgg1/yuRxxxBE9fn2eeOIJNt54Y3bddVdWWmklZs+ezcMPP8w555zDggULAHj88cc58cQTmThxInvuuScrrLACL7zwAg8//DBnnHEGzz//PGPHjuXee+/l17/+NTvttBP77LMPUKwZddNNNy1+vfPOO4/p06ez1VZbsdNOOzFr1iyuv/76btefWnvttbnkkkt6/f7+4Q9/YN68ebzpTW9i+PDhTJ48mXPOOYePfvSji5/z9NNPM3/+fHbaaSdWXnll5s6dy2OPPcaJJ57IDTfcMCh/Ps2vd/5gEz3dXWAw2mabbfKWW27p72Est0mTJpUfTSnfYNKkif0yFkmS+sOkSdeUH40v35r//6g6aHy/GuvIzJkzh5VXXrltv7jPmDGjrScG5ptvvvnmm7+s+Y2idfz48YwfPx6o/+8zEXFrZm7TdbuX10mSJKnfdHR01PpSCPPNN9988833UrueWTpJkiSpX9X9xMB8880333zzLZ66Z+kkSZKkflf3EwPzzTfffPPNt3h6KUsnSZIkDQh1PzEw33zzzTfffIunJVk6SZIkacCo+4mB+eabb7755ls8vcjSSZIkSQNK3U8MzDfffPPNN9/iqWDpJEmSpAGn7icG5ptvvvnmm2/xZOkkSZKkAaruJwbmm2+++eabP9SLJ0snSZIkDVh1PzEw33zzzTff/KFcPFk6SZIkaUCr+4mB+eabb7755g/V4snSSZIkSQNe3U8MzDfffPPNN38oFk+WTpIkSaqFup8YmG+++eabb/5QK54snSRJklQbdT8xMN9888033/w5c+ZUnj9QWTpJkiSpVgbTiYf55ptvvvlDL3/q1KlDpniydJIkSVLtDJYTD/PNN99884defmdnJ1OnTmXevHmV5w80lk6SJEmqpcFw4mG++eabb/7Qy+/o6KCzs5Pp06dXnj3QWDpJkiSptup+4mG++eabb/7QzO/o6GDcuHGV5w40lk6SJEmqtbqfeJhvvvnmmz8080eNGlV55kBj6SRJkqTaq/uJh/nmm2+++UMzf7CzdJIkSVKfa8fiqXU/8TDffPPNN39o5g9mlk6SJEnqc9OnT2/L7aLrfuJhvvnmm2/+0MwfrCydJEmS1OfGjRvH1KlTLZ7MN9988803fxCzdJIkSVKfGzVqFJ2dnUydOrWWJwbmm2+++eabb/G0dJZOkiRJ6hcdHR10dnbW9sTAfPPNN9988y2eemfpJEmSpH7T0dFR6xMD880333zzzbd46pmlkyRJkvpV3U8MzDfffPPNN9/iqXuWTpIkSep3dT8xMN98880333yLp5eydJIkSdKAUPcTA/PNN9988823eFqSpZMkSZIGjLqfGJhvvvnmm2++xdOLLJ0kSZI0oNT9xMB8880333zzLZ4Klk6SJEkacOp+YmC++eabb775Fk+WTpIkSRqg6n5iYL755ptvvvlDvXiydJIkSdKAVfcTA/PNN998880fysWTpZMkSZIGtLqfGJhvvvnmm2/+UC2eLJ0kSZI04NX9xMB8880333zzh2LxZOkkSZKkWqj7iYH55ptvvvnmD7XiydJJkiRJtVH3EwPzzTfffPPNnzNnTuX5A5WlkyRJkmplMJ14mG+++eabP/Typ06dOmSKJ0snSZIk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OkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmSJEmSJKlylk6SJEmSJEmqnKWTJEmSJEmSKmfpJEmSJEmSpMpZOkmS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\n",
"text/plain": [
"<Figure size 1440x720 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"runtime_diff = no_scalar.average - yes_scalar.average\n",
"\n",
"\n",
"fig, ax = plt.subplots(figsize=(20,10))\n",
"bars = ax.bar([1,2], height=[no_scalar.average, yes_scalar.average], edgecolor='black', lw=5)\n",
"diff_bar = ax.bar([2], \n",
" height=runtime_diff, \n",
" bottom=bars[1].get_height(), \n",
" color='black', \n",
" alpha=0.5, \n",
" hatch='\\\\', \n",
" edgecolor='black', \n",
" lw=5)\n",
" \n",
"\n",
"plt.xticks(fontsize=16)\n",
"plt.yticks(fontsize=16)\n",
"\n",
"\n",
"bars[0].set_color('yellow')\n",
"bars[1].set_color('green')\n",
"\n",
"bars[0].set_alpha(0.5)\n",
"bars[1].set_alpha(0.5)\n",
"\n",
"bars[0].set_edgecolor('black')\n",
"bars[1].set_edgecolor('black')\n",
"\n",
"ax.set_xticks([1,2])\n",
"ax.set_xticklabels(['Python Scalar', 'cudf.Scalar'], rotation=45)\n",
"\n",
"plt.ylabel('Average Runtime (seconds)', fontsize=16)\n",
"\n",
"ax.annotate(str(round(runtime_diff, 3)) + \" seconds\", \n",
" xy=(1.91, bars[1].get_height() + runtime_diff / 2),\n",
" xycoords='data',\n",
" color='white',\n",
" fontsize=16)\n",
"title = f\"Runtime Difference = {round(100 * runtime_diff / bars[0].get_height(), 3)}%\"\n",
"\n",
"plt.title(title, fontsize=24)\n",
"\n",
"plt.show()\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.9"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment