diff --git a/ci/test_notebooks.sh b/ci/test_notebooks.sh index 7f5f35219b0..c9dc99733a9 100755 --- a/ci/test_notebooks.sh +++ b/ci/test_notebooks.sh @@ -34,7 +34,7 @@ pushd notebooks # Add notebooks that should be skipped here # (space-separated list of filenames without paths) -SKIPNBS="" +SKIPNBS="performance_comparisons.ipynb" EXITCODE=0 trap "EXITCODE=1" ERR diff --git a/docs/cudf/source/conf.py b/docs/cudf/source/conf.py index b97879b0ae4..af57f9245a8 100644 --- a/docs/cudf/source/conf.py +++ b/docs/cudf/source/conf.py @@ -45,6 +45,8 @@ "myst_nb", ] +nb_execution_excludepatterns = ['performance-comparisons.ipynb'] + nb_execution_mode = "force" nb_execution_timeout = 300 diff --git a/docs/cudf/source/user_guide/index.md b/docs/cudf/source/user_guide/index.md index d3ead13132f..0d74586e7a8 100644 --- a/docs/cudf/source/user_guide/index.md +++ b/docs/cudf/source/user_guide/index.md @@ -12,6 +12,7 @@ groupby guide-to-udfs cupy-interop options +performance-comparisons PandasCompat copy-on-write ``` diff --git a/docs/cudf/source/user_guide/performance_comparisons.ipynb b/docs/cudf/source/user_guide/performance_comparisons.ipynb new file mode 100644 index 00000000000..3dd671c37cc --- /dev/null +++ b/docs/cudf/source/user_guide/performance_comparisons.ipynb @@ -0,0 +1,1647 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Performance comparison" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook compares the performance of `cuDF` and `pandas`. The comparisons performed are on identical data sizes. This notebook primarily showcases the factor\n", + "of speedups users can have when the similar `pandas` APIs are run on GPUs using `cudf`.\n", + "\n", + "The hardware details used to run these performance comparisons are at the end of this page." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import os\n", + "import time\n", + "import timeit\n", + "from io import BytesIO\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "import cudf" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "np.random.seed(0)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## Concat, count & joins performance" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
numbersbusiness
0-316Costco
1-441Costco
2653Buckees
3216Buckees
4-165Walmart
.........
299999995-395Walmart
299999996-653Buckees
299999997364Buckees
299999998159Buckees
299999999-501Walmart
\n", + "

300000000 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " numbers business\n", + "0 -316 Costco\n", + "1 -441 Costco\n", + "2 653 Buckees\n", + "3 216 Buckees\n", + "4 -165 Walmart\n", + "... ... ...\n", + "299999995 -395 Walmart\n", + "299999996 -653 Buckees\n", + "299999997 364 Buckees\n", + "299999998 159 Buckees\n", + "299999999 -501 Walmart\n", + "\n", + "[300000000 rows x 2 columns]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "num_rows = 300_000_000\n", + "pdf = pd.DataFrame(\n", + " {\n", + " \"numbers\": np.random.randint(-1000, 1000, num_rows, dtype=\"int64\"),\n", + " \"business\": np.random.choice(\n", + " [\"McD\", \"Buckees\", \"Walmart\", \"Costco\"], size=num_rows\n", + " ),\n", + " }\n", + ")\n", + "pdf" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
numbersbusiness
0-316Costco
1-441Costco
2653Buckees
3216Buckees
4-165Walmart
.........
299999995-395Walmart
299999996-653Buckees
299999997364Buckees
299999998159Buckees
299999999-501Walmart
\n", + "

300000000 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " numbers business\n", + "0 -316 Costco\n", + "1 -441 Costco\n", + "2 653 Buckees\n", + "3 216 Buckees\n", + "4 -165 Walmart\n", + "... ... ...\n", + "299999995 -395 Walmart\n", + "299999996 -653 Buckees\n", + "299999997 364 Buckees\n", + "299999998 159 Buckees\n", + "299999999 -501 Walmart\n", + "\n", + "[300000000 rows x 2 columns]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gdf = cudf.from_pandas(pdf)\n", + "gdf" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def timeit_pandas_cudf(pd_obj, gd_obj, func, **kwargs):\n", + " \"\"\"\n", + " A utility function to measure execution time of an\n", + " API(`func`) in pandas & cudf.\n", + "\n", + " Parameters\n", + " ----------\n", + " pd_obj : Pandas object\n", + " gd_obj : cuDF object\n", + " func : callable\n", + " \"\"\"\n", + " pandas_time = timeit.timeit(lambda: func(pd_obj), **kwargs)\n", + " cudf_time = timeit.timeit(lambda: func(gd_obj), **kwargs)\n", + " return pandas_time, cudf_time" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "pandas_value_counts, cudf_value_counts = timeit_pandas_cudf(\n", + " pdf, gdf, lambda df: df.value_counts(), number=30\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "pdf = pdf.head(100_000_000)\n", + "gdf = gdf.head(100_000_000)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "pandas_concat = timeit.timeit(lambda: pd.concat([pdf, pdf, pdf]), number=30)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "cudf_concat = timeit.timeit(lambda: cudf.concat([gdf, gdf, gdf]), number=30)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "pandas_groupby, cudf_groupby = timeit_pandas_cudf(\n", + " pdf,\n", + " gdf,\n", + " lambda df: df.groupby(\"business\").agg([\"min\", \"max\", \"mean\"]),\n", + " number=30,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "num_rows = 1_000_000\n", + "pdf = pd.DataFrame(\n", + " {\n", + " \"numbers\": np.random.randint(-1000, 1000, num_rows, dtype=\"int64\"),\n", + " \"business\": np.random.choice(\n", + " [\"McD\", \"Buckees\", \"Walmart\", \"Costco\"], size=num_rows\n", + " ),\n", + " }\n", + ")\n", + "gdf = cudf.from_pandas(pdf)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "pandas_merge, cudf_merge = timeit_pandas_cudf(\n", + " pdf, gdf, lambda df: df.merge(df), number=30\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "performance_df = pd.DataFrame(\n", + " {\n", + " \"cudf speedup vs. pandas\": [\n", + " pandas_value_counts / cudf_value_counts,\n", + " pandas_concat / cudf_concat,\n", + " pandas_groupby / cudf_groupby,\n", + " pandas_merge / cudf_merge,\n", + " ],\n", + " },\n", + " index=[\"value_counts\", \"concat\", \"groupby\", \"merge\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
cudf speedup vs. pandas
value_counts282.901300
concat203.624680
groupby138.495762
merge136.519031
\n", + "
" + ], + "text/plain": [ + " cudf speedup vs. pandas\n", + "value_counts 282.901300\n", + "concat 203.624680\n", + "groupby 138.495762\n", + "merge 136.519031" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "performance_df" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "image/png": "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+m3fvh2PHj0q8dmWr4Jyhx1HR0ecPHkSGRkZsLKyKjZy8uOPP75yJzwREVWlmJgYdOnSRXo9ceJEAEBgYCBWrlyJa9euYf369fj3339hb2+PNm3a4OTJk2jSpAkAwNTUFHv37sVnn30Gf39/ZGVlwcvLC+vXr8ebb75pkG0i/Xp6JO9pa9asQd++feHo6GiAqgxP58dFqNXqEtufvrMxERHp3/NGw3/66afnLsPb2xs7duzQZ1lUSSo6klfkxo0bOHHiBPbu3VvVm1Bt6Bx2iIiIqPJVdCSvyPfff4+aNWvqfKqJnChEWf9NeEVkZmZCrVYjIyMDNjY2Vb7+KaXf/1DW5r+iP3nc368W7m+iylPev9/V/tlYRERERBXBw1hERER6wpG86okjO0RERCRrDDtEREQkaww7REREJGsMO0RERCRrDDtEREQkaww7REREJGsMO0RERCRrDDtEREQkaww7REREJGsMO0RERCRrDDtEREQkaww7REREJGsMO0RERCRrDDtEREQkaww7REREJGsMO0RERCRrDDtEREQkaww7REREJGsMO0RERCRrDDtEREQkaww7REREJGsMO0RERCRrDDtEREQkaww7REREJGsMO0RERCRrDDtEREQkaww7REREJGsMO0RERCRrDDtEREQkawYNOydOnIC/vz/c3NygUCiwc+dOrflCCISFhcHNzQ0qlQqdO3fGH3/8odVHo9Fg3LhxcHBwgKWlJfr27Yu7d+9W4VYQERFRdWbQsJOdnY3mzZsjIiKixPnz5s3DwoULERERgfPnz8PFxQU9evTAw4cPpT4hISGIiopCZGQkTp06haysLPTp0wcFBQVVtRlERERUjZkYcuV+fn7w8/MrcZ4QAosXL8a0adMwcOBAAMD69evh7OyMzZs3Y8SIEcjIyMCaNWvwww8/oHv37gCAjRs3wt3dHYcPH0avXr2qbFuIiIioeqq25+wkJSUhNTUVPXv2lNqUSiU6deqE06dPAwBiY2ORl5en1cfNzQ0+Pj5Sn5JoNBpkZmZqTURERCRP1TbspKamAgCcnZ212p2dnaV5qampMDMzg62tbal9ShIeHg61Wi1N7u7ueq6eiIiIqotqG3aKKBQKrddCiGJtz3pen9DQUGRkZEhTcnKyXmolIiKi6qfahh0XFxcAKDZCk5aWJo32uLi4IDc3F+np6aX2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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "ax = performance_df.plot.bar(\n", + " color=\"#7400ff\",\n", + " ylim=(1, 400),\n", + " rot=0,\n", + " xlabel=\"Operation\",\n", + " ylabel=\"Speedup factor\",\n", + ")\n", + "ax.bar_label(ax.containers[0], fmt=\"%.0f\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Cleaning up used memory for later benchmarks\n", + "del pdf\n", + "del gdf\n", + "import gc\n", + "\n", + "_ = gc.collect()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Strings Performance" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "pd_series = pd.Series(\n", + " np.random.choice(\n", + " [\"123\", \"56.234\", \"Walmart\", \"Costco\", \"rapids ai\"], size=300_000_000\n", + " )\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "gd_series = cudf.from_pandas(pd_series)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "pandas_upper, cudf_upper = timeit_pandas_cudf(\n", + " pd_series, gd_series, lambda s: s.str.upper(), number=20\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "pandas_contains, cudf_contains = timeit_pandas_cudf(\n", + " pd_series, gd_series, lambda s: s.str.contains(r\"[0-9][a-z]\"), number=20\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "pandas_isalpha, cudf_isalpha = timeit_pandas_cudf(\n", + " pd_series, gd_series, lambda s: s.str.isalpha(), number=20\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "performance_df = pd.DataFrame(\n", + " {\n", + " \"cudf speedup vs. pandas\": [\n", + " pandas_upper / cudf_upper,\n", + " pandas_contains / cudf_contains,\n", + " pandas_isalpha / cudf_isalpha,\n", + " ],\n", + " },\n", + " index=[\"upper\", \"contains\", \"isalpha\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
cudf speedup vs. pandas
upper1832.120875
contains1311.758332
is_alpha5752.301339
\n", + "
" + ], + "text/plain": [ + " cudf speedup vs. pandas\n", + "upper 1832.120875\n", + "contains 1311.758332\n", + "is_alpha 5752.301339" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "performance_df" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "image/png": "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4u7du3o1Li4uaNmypVSzb98+qNVqKQwBQPv27aFWq6WaihQVFSE/P1/vRURERHVTjQYif39/fPvtt9i8eTPi4uKQnZ2NDh064Nq1a8jOzgYAODk56W3j5OQkrcvOzoaZmRlsbW0fWePo6Fju2I6OjlJNRWbMmCHNOVKr1XBzc3uqcyUiIqLaq0YDUa9evfDaa6/B19cX3bt3xy+//ALg/qWxMgqFQm8bIUS5tgc9WFNR/eP2M3nyZOh0Oul18eLFSp0TERERPXtq/JLZ31laWsLX1xenT5+W5hU9OIqTk5MjjRppNBoUFxcjLy/vkTVXr14td6zc3Nxyo09/p1QqYWNjo/ciIiKiuqlWBaKioiKkp6fD2dkZXl5e0Gg02Lp1q7S+uLgYO3fuRIcOHQAAbdu2hampqV5NVlYWjh07JtUEBARAp9PhwIEDUs3+/fuh0+mkGiIiIpK3Gr3LbPz48ejTpw/c3d2Rk5ODadOmIT8/H0OGDIFCoUBUVBSmT58Ob29veHt7Y/r06bCwsEBYWBgAQK1WIyIiAuPGjYO9vT3s7Owwfvx46RIcAPj4+CA4OBjDhg3DkiVLAADDhw9HSEgI7zAjIiIiADUciC5duoRBgwbhr7/+QsOGDdG+fXskJyfDw8MDADBx4kQUFhZi5MiRyMvLg7+/P7Zs2QJra2tpHzExMahfvz4GDBiAwsJCdOvWDfHx8ahXr55Us3r1akRGRkp3o4WGhiI2NrZ6T5aIiIhqLYUQQtR0J54F+fn5UKvV0Ol0nE9ERPSMm/Doe3Ookr56BhJEZT+/a9UcIiIiIqKawEBEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREsmdQILp37x4+/fRTXLx4sar6Q0RERFTtDApE9evXx1dffYWSkpKq6g8RERFRtTP4kln37t2xY8eOKugKERERUc2ob+gGvXr1wuTJk3Hs2DG0bdsWlpaWeutDQ0ON1jkiIiKi6qAQQghDNjAxefigkkKhqLOX0/Lz86FWq6HT6WBjY1PT3SEioqcwQVHTPagbvjIoQdSMyn5+GzxC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HLK+7b60tPSpOkZERERU2xg8h4iIiIiorql0INq/fz9+++03vbZvv/0WXl5ecHR0xPDhw1FUVGT0DhIRERFVtUoHoujoaBw9elRaTktLQ0REBLp3744PP/wQP/30E2bMmFElnSQiIiKqSpUORFqtFt26dZOWExIS4O/vj7i4OIwdOxbz58/HunXrqqSTRERERFWp0oEoLy8PTk5O0vLOnTsRHBwsLb/44ou4ePGicXtHREREVA0qHYicnJyQmZkJACguLsahQ4cQEBAgrb958yZMTU2N30MiIiKiKlbpQBQcHIwPP/wQu3fvxuTJk2FhYaH3ZOqjR4+icePGVdJJIiIioqpU6ecQTZs2Da+++io6d+4MKysrrFixAmZmZtL6//znP+jRo0eVdJKIiIioKlU6EDVs2BC7d++GTqeDlZUV6tWrp7d+/fr1sLKyMnoHiYiIiKqawU+qVqvVFbbb2dk9dWeIiIiIagKfVE1ERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyV6OBaMaMGXjxxRdhbW0NR0dH9OvXDxkZGXo1QghER0fDxcUFKpUKgYGBOH78uF5NUVERxowZAwcHB1haWiI0NBSXLl3Sq8nLy0N4eDjUajXUajXCw8Nx48aNqj7FOmnXrl3o06cPXFxcoFAosHHjRr31BQUFGD16NFxdXaFSqeDj44NFixbp1bz77rto3LgxVCoVGjZsiL59++LkyZPS+vPnzyMiIgJeXl5QqVRo3Lgxpk6diuLi4uo4RSIikpkaDUQ7d+7EqFGjkJycjK1bt+LevXvo0aMHbt26JdXMmjULc+fORWxsLFJSUqDRaBAUFISbN29KNVFRUUhMTERCQgL27NmDgoIChISEoKSkRKoJCwuDVqtFUlISkpKSoNVqER4eXq3nW1fcunULfn5+iI2NrXD9Bx98gKSkJKxatQrp6en44IMPMGbMGPz4449STdu2bbF8+XKkp6dj8+bNEEKgR48e0u/s5MmTKC0txZIlS3D8+HHExMRg8eLF+Oijj6rlHImISF4UQghR050ok5ubC0dH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2bP45ptvMGLECCP2kohqAgMREclGly5doNVqsXfvXgQHB2PAgAHIycl5on1duXIFwcHB6N+/P9555x0j95SIqhsDERHJhqWlJZo0aYL27dtj2bJlqF+/PpYtW2bwfq5cuYIuXbogICAAS5curYKeElF1YyAiItkSQqCoqMigbS5fvozAwEC88MILWL58OUxM+GeUqC6oX9MdICIyhoKCApw5c0ZazszMhFarhZ2dHezt7fHFF18gNDQUzs7OuHbtGhYuXIhLly6hf//+0jbZ2dnIzs6W9pOWlgZra2u4u7vDzs4OV65cQWBgINzd3TF79my9W/Y1Gk31nSwRGR0DERHVCQcPHkSXLl2k5bFjxwIAhgwZgsWLF+PkyZNYsWIF/vrrL9jb2+PFF1/E7t270aJFC2mbxYsX49NPP5WWO3XqBABYvnw5hg4dii1btuDMmTM4c+YMXF1d9Y7PJ5gQPdv4HKJK4nOI5OdZeL4GET0Z/q00jmfh7ySfQ0RERERUSbxkRkRViv8nbjzPwv+NEz2rOEJEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyV6OBaNeuXejTpw9cXFygUCiwceNGvfVCCERHR8PFxQUqlQqBgYE4fvy4Xk1RURHGjBkDBwcHWFpaIjQ0FJcuXdKrycvLQ3h4ONRqNdRqNcLDw3Hjxo0qPjsiIiJ6VtRoILp16xb8/PwQGxtb4fpZs2Zh7ty5iI2NRUpKCjQaDYKCgnDz5k2pJioqComJiUhISMCePXtQUFCAkJAQlJSUSDVhYWHQarVISkpCUlIStFotwsPDq/z8iIiI6NlQvyYP3qtXL/Tq1avCdUIIzJs3Dx9//DFeffVVAMCKFSvg5OSENWvW4N1334VOp8OyZcuwcuVKdO/eHQCwatUquLm5Ydu2bejZsyfS09ORlJSE5ORk+Pv7AwDi4uIQEBCAjIwMNG3atHpOloiIiGqtWjuHKDMzE9nZ2ejRo4fUplQq0blzZ+zduxcAkJqairt37+rVuLi4oGXLllLNvn37oFarpTAEAO3bt4darZZqKlJUVIT8/Hy9FxEREdVNtTYQZWdnAwCcnJz02p2cnKR12dnZMDMzg62t7SNrHB0dy+3f0dFRqqnIjBkzpDlHarUabm5uT3U+REREVHvV2kBURqFQ6C0LIcq1PejBmorqH7efyZMnQ6fTSa+LFy8a2HMiIiJ6VtTaQKTRaACg3ChOTk6ONGqk0WhQXFyMvLy8R9ZcvXq13P5zc3PLjT79nVKphI2Njd6LiIiI6qZaG4i8vLyg0WiwdetWqa24uBg7d+5Ehw4dAABt27aFqampXk1WVhaOHTsm1QQEBECn0+HAgQNSzf79+6HT6aQaIiIikrcavcusoKAAZ86ckZYzMzOh1WphZ2cHd3d3REVFYfr06fD29oa3tzemT58OCwsLhIWFAQDUajUiIiIwbtw42Nvbw87ODuPHj4evr69015mPjw+Cg4MxbNgwLFmyBAAwfPhwhISE8A4zIiIiAlDDgejgwYPo0qWLtDx27FgAwJAhQxAfH4+JEyeisLAQI0eORF5eHvz9/bFlyxZYW1tL28TExKB+/foYMGAACgsL0a1bN8THx6NevXpSzerVqxEZGSndjRYaGvrQZx8RERGR/CiEEKKmO/EsyM/Ph1qthk6nq9XziSY8er45GeAr/sswCr4njYfvSePh+9I4noX3ZGU/v2vtHCIiIiKi6sJARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyx0BEREREssdARERERLLHQERERESyV7+mO/CsEEIAAPLz82u4J49WVNMdqENq+a/6mcH3pPHwPWk8fF8ax7Pwniz73C77HH8YhXhcBQEALl26BDc3t5ruBhERET2BixcvwtXV9aHrGYgqqbS0FFeuXIG1tTUUCkVNd+eZlZ+fDzc3N1y8eBE2NjY13R0iAHxfUu3D96TxCCFw8+ZN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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "ax = performance_df.plot.bar(\n", + " color=\"#7400ff\",\n", + " ylim=(1, 7000),\n", + " rot=0,\n", + " xlabel=\"String method\",\n", + " ylabel=\"Speedup factor\",\n", + ")\n", + "ax.bar_label(ax.containers[0], fmt=\"%.0f\")\n", + "plt.show()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## User-defined function (UDF) performance (with JIT overhead)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The first UDF runs include JIT compilation overhead, due to which the performance of first run and average of next few runs are compared separately." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
age
087
171
263
340
492
......
99999954
999999628
999999731
99999984
999999947
\n", + "

10000000 rows × 1 columns

\n", + "
" + ], + "text/plain": [ + " age\n", + "0 87\n", + "1 71\n", + "2 63\n", + "3 40\n", + "4 92\n", + "... ...\n", + "9999995 4\n", + "9999996 28\n", + "9999997 31\n", + "9999998 4\n", + "9999999 47\n", + "\n", + "[10000000 rows x 1 columns]" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "num_rows = 10_000_000\n", + "pdf_age = pd.DataFrame(\n", + " {\n", + " \"age\": np.random.randint(0, 100, num_rows),\n", + " }\n", + ")\n", + "pdf_age" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
age
087
171
263
340
492
......
99999954
999999628
999999731
99999984
999999947
\n", + "

10000000 rows × 1 columns

\n", + "
" + ], + "text/plain": [ + " age\n", + "0 87\n", + "1 71\n", + "2 63\n", + "3 40\n", + "4 92\n", + "... ...\n", + "9999995 4\n", + "9999996 28\n", + "9999997 31\n", + "9999998 4\n", + "9999999 47\n", + "\n", + "[10000000 rows x 1 columns]" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gdf_age = cudf.from_pandas(pdf_age)\n", + "gdf_age" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def age_udf(row):\n", + " if row[\"age\"] < 18:\n", + " return 0\n", + " elif 18 <= row[\"age\"] < 20:\n", + " return 1\n", + " elif 20 <= row[\"age\"] < 30:\n", + " return 2\n", + " elif 30 <= row[\"age\"] < 40:\n", + " return 3\n", + " elif 40 <= row[\"age\"] < 50:\n", + " return 4\n", + " elif 50 <= row[\"age\"] < 60:\n", + " return 5\n", + " elif 60 <= row[\"age\"] < 70:\n", + " return 6\n", + " else:\n", + " return 7" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [], + "source": [ + "pandas_int_udf, cudf_int_udf = timeit_pandas_cudf(\n", + " pdf_age, gdf_age, lambda df: df.apply(age_udf, axis=1), number=1\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "def str_isupper_udf(row):\n", + " if row.isupper():\n", + " return 0\n", + " else:\n", + " return 1" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0 AI\n", + "1 ABC\n", + "2 hello world\n", + "3 abc\n", + "4 hello world\n", + " ... \n", + "99999995 AI\n", + "99999996 AI\n", + "99999997 abc\n", + "99999998 abc\n", + "99999999 hello world\n", + "Name: strings, Length: 100000000, dtype: object" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd_series = pd.Series(\n", + " np.random.choice([\"ABC\", \"abc\", \"hello world\", \"AI\"], size=100_000_000),\n", + " name=\"strings\",\n", + ")\n", + "pd_series" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0 AI\n", + "1 ABC\n", + "2 hello world\n", + "3 abc\n", + "4 hello world\n", + " ... \n", + "99999995 AI\n", + "99999996 AI\n", + "99999997 abc\n", + "99999998 abc\n", + "99999999 hello world\n", + "Name: strings, Length: 100000000, dtype: object" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gd_series = cudf.from_pandas(pd_series)\n", + "gd_series" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [], + "source": [ + "pandas_str_udf, cudf_str_udf = timeit_pandas_cudf(\n", + " pd_series, gd_series, lambda s: s.apply(str_isupper_udf), number=1\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
cudf speedup vs. pandas
Numeric362.091673
String204.865789
\n", + "
" + ], + "text/plain": [ + " cudf speedup vs. pandas\n", + "Numeric 362.091673\n", + "String 204.865789" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "performance_df = pd.DataFrame(\n", + " {\n", + " \"cudf speedup vs. pandas\": [\n", + " pandas_int_udf / cudf_int_udf,\n", + " pandas_str_udf / cudf_str_udf,\n", + " ]\n", + " },\n", + " index=[\"Numeric\", \"String\"],\n", + ")\n", + "performance_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Below is the plot showing performance speedup in case of Numeric UDFs & String UDFs on their first runs." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGwCAYAAABPSaTdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy88F64QAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA98klEQVR4nO3deXxM9/7H8fdk30OCLARR+660KXUttbd2t7S0pVQpqrmlXHUVlyZoLVW7aqPWS5VLF7W0Uq5qCSlKKU2LSppWI7FEgpzfHx6ZX6exZJKJxMnr+XjM42G+5ztnPidM5u18v+d7LIZhGAIAADApp8IuAAAAoCARdgAAgKkRdgAAgKkRdgAAgKkRdgAAgKkRdgAAgKkRdgAAgKm5FHYBRUFWVpbOnj0rX19fWSyWwi4HAADkgmEYunDhgkJDQ+XkdOvzN4QdSWfPnlVYWFhhlwEAAPLg9OnTKleu3C23E3Yk+fr6Srrxw/Lz8yvkagAAQG6kpaUpLCzM+j1+K4QdyTp05efnR9gBAOAec6cpKExQBgAApkbYAQAApkbYAQAApsacHQCww/Xr13X16tXCLgMoFlxdXeXs7Jzv/RB2ACAXDMNQUlKSzp8/X9ilAMVKiRIlFBwcnK918Ag7AJAL2UGnTJky8vLyYgFSoIAZhqHLly8rOTlZkhQSEpLnfRF2AOAOrl+/bg06gYGBhV0OUGx4enpKkpKTk1WmTJk8D2kxQRkA7iB7jo6Xl1chVwIUP9mfu/zMlSPsAEAuMXQF3H2O+NwRdgAAgKkRdgAAgKkxQRkA8uGVuzyy9YZxd9/vZmJiYhQZGWlzGf6iRYs0adIk/fLLL5oxY4YiIyMLrT57/PTTTwoPD9eBAwdUv379wi7nnlWxYkVFRkYW2b93wg4AIF/S0tI0bNgwzZgxQz169JC/v39hlwTYIOwAAPLl1KlTunr1qh577LF8rYUCFBTm7ACAiWVlZWnq1KmqXLmy3N3dVb58eb3++uuSpB07dshisdgMR8XHx8tiseinn36ytsXExKh8+fLy8vJSt27ddO7cOZttderUkSRVqlQpx2uzZWZmatiwYQoJCZGHh4cqVqyo6Oho63aLxaL58+erQ4cO8vT0VHh4uNauXWuzj19++UW9evVSyZIlFRgYqC5duuR4r/fee081atSQh4eHqlevrnnz5tls/+abb9SgQQN5eHioUaNGOnDggM32mJgYlShRwqZtw4YNNlcETZgwQfXr19fChQsVFhYmLy8vPf7447dcXTsrK0vlypXTggULbNr3798vi8WiH3/80brf8uXLy93dXaGhoRo+fPhN93czP/30kywWi1avXq0mTZrIw8NDtWrV0o4dO6x9rl+/rgEDBig8PFyenp6qVq2a3nrrLZv99OvXT127dtWbb76pkJAQBQYGaujQoTaXfScnJ6tTp07Wv6cVK1bkqGfGjBmqU6eOvL29FRYWpiFDhujixYvW7T///LM6deqkkiVLytvbW7Vq1dInn3yS6+O1F2EHAExszJgxmjp1qsaNG6cjR45o5cqVCgoKyvXrv/76a/Xv319DhgxRfHy8WrZsqcmTJ1u39+rVS9u2bZN0I0gkJiYqLCwsx35mz56tjRs3as2aNTp27JiWL1+uihUr2vQZN26cevTooW+//VZPPfWUnnzySR09elSSdPnyZbVs2VI+Pj768ssvtWvXLvn4+Kh9+/bKzMyUJC1evFhjx47V66+/rqNHjyoqKkrjxo3T0qVLJUmXLl1Sx44dVa1aNcXFxWnChAkaOXKkXT/PbCdOnNCaNWu0adMmbd68WfHx8Ro6dOhN+zo5OemJJ57IEQpWrlypxo0bq1KlSvrggw80c+ZMLVy4UD/88IM2bNhgDZH2eOWVVzRixAgdOHBATZo0UefOna3hNDt0rVmzRkeOHNFrr72mV199VWvWrLHZxxdffKGTJ0/qiy++0NKlSxUTE6OYmBjr9n79+umnn37S559/rg8++EDz5s2zrnL852OePXu2Dh8+rKVLl+rzzz/XqFGjrNuHDh2qjIwMffnllzp06JCmTp0qHx8fu4831wwYqamphiQjNTW1sEsBUASlp6cbR44cMdLT03NsG6m7+7BHWlqa4e7ubixevPim27/44gtDkpGSkmJtO3DggCHJSEhIMAzDMJ588kmjffv2Nq/r1auX4e/vf8vX3MyLL75oPPLII0ZWVtZNt0syBg8ebNMWERFhvPDCC4ZhGMaSJUuMatWq2bw+IyPD8PT0ND777DPDMAwjLCzMWLlypc0+Jk2aZDRu3NgwDMNYuHChERAQYFy6dMm6ff78+YYk48CBA4ZhGMZ7771nc2yGYRjr1683/vx1OX78eMPZ2dk4ffq0te3TTz81nJycjMTExJse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np6eatGihb777jubPi1atJDFYrF5sD5c8WB32AkNDVV8fLxGjhypQYMGqUGDBpoyZYoOHDhwz6+3AwAoemJjYzV06FDt2bNHW7du1bVr19S2bVub+zFOmzZNM2bM0Jw5c7R3714FBwerTZs2unDhgs2+Bg4cqMTEROtj4cKFd/twUAjytaigWbCoIIoLFhWEGfz2228qU6aMYmNj1axZMxmGodDQUEVGRmr06NGSpIyMDAUFBWnq1KkaNGiQpBtndurXr28zFQP3tgJdVPDYsWMaNmyYWrVqpdatW2vYsGH6/vvv81wsAAC5lZqaKkkKCAiQJCUkJCgpKUlt27a19nF3d1fz5s21e/dum9euWLFCpUqVUq1atTRy5MgcZ35gTnaHnQ8++EC1a9dWXFyc6tWrp7p162r//v2qU6eO1q5dWxA1AgAg6cbcnJdffllNmzZV7dq1JUlJSUmSpKCgIJu+QUFB1m2S1KdPH61atUo7duzQuHHjtG7dOnXv3v3uFY9CY/c6O6NGjdKYMWP073//26Z9/PjxGj16tB5//HGHFQcAwJ8NGzZMBw8e1K5du3Jss1hsx+UNw7BpGzhwoPXPtWvXVpUqVdSoUSPt379f999/f8EVjUJn95mdpKQkPfPMMznan3rqKZsEDQCAI7344ovauHGjvvjiC5UrV87aHhwcLEk5voOSk5NznO35s/vvv1+urq764YcfCqZgFBl2h50WLVpo586dOdp37dqlv/3tbw4pCgCAbIZhaNiwYfrwww/1+eefKzw83GZ7eHi4goODtXXrVmtbZmamYmNj1aRJk1vu97vvvtPVq1cVEhJSYLWjaLB7GKtz584aPXq04uLi9NBDD0mS9uzZo7Vr12rixInauHGjTV8AAPJj6NChWrlypf773//K19fXegbH399fnp6eslgsioyMVFRUlKpUqaIqVaooKipKXl5e6t27tyTp5MmTWrFihR599FGVKlVKR44c0YgRI9SgQQM9/PDDhXl4uAvsvvTcySl3J4MsFstNbytRFHHpOYoLLj3Hveivc3Gyvffee+rXr5+kG2d/Jk6cqIULFyolJUURERGaO3eudRLz6dOn9dRTT+nw4cO6ePGiwsLC9Nhjj2n8+PHWq7pw78nt9zfr7Iiwg+KDsAPATAp0nR0AAIB7Ra7n7Hz99df6448/1KFDB2vb+++/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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "ax = performance_df.plot.bar(\n", + " color=\"#7400ff\",\n", + " ylim=(1, 550),\n", + " rot=0,\n", + " xlabel=\"UDF Kind\",\n", + " ylabel=\"Speedup factor\",\n", + ")\n", + "ax.bar_label(ax.containers[0], fmt=\"%.0f\")\n", + "plt.show()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## User-defined function (UDF) performance (without JIT overhead)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "pandas_int_udf, cudf_int_udf = timeit_pandas_cudf(\n", + " pdf_age, gdf_age, lambda df: df.apply(age_udf, axis=1), number=10\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [], + "source": [ + "pandas_str_udf, cudf_str_udf = timeit_pandas_cudf(\n", + " pd_series, gd_series, lambda s: s.apply(str_isupper_udf), number=10\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
cudf speedup vs. pandas
Numeric95448.144630
String2587.570338
\n", + "
" + ], + "text/plain": [ + " cudf speedup vs. pandas\n", + "Numeric 95448.144630\n", + "String 2587.570338" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "performance_df = pd.DataFrame(\n", + " {\n", + " \"cudf speedup vs. pandas\": [\n", + " pandas_int_udf / cudf_int_udf,\n", + " pandas_str_udf / cudf_str_udf,\n", + " ]\n", + " },\n", + " index=[\"Numeric\", \"String\"],\n", + ")\n", + "performance_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Below is the plot showing performance speedup in case of Numeric UDFs & String UDFs on their consequent runs. In this case the speedup is massive because of no JIT overhead present." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "image/png": "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YAX8zapVq2QYhsqXL6+DBw9q6NChKl++vF566SWdPXtWI0eOVIcOHRQQEKDDhw/rzTfflI+Pj9q1ayfpygz91zthvUSJEuaoV4kSJRzWFSlSRNKV0bKr959q2rSphg4dqv79++uVV15RVlaWxo0bpwIFCuiZZ565mx8BAOAOcCgQ+JvU1FT1799fFSpU0Isvvqj69etr9erV5mW4u3fvVps2bVSuXDl169ZN5cqV0+bNm29576jcqlChgpYtW6Zdu3apTp06euqpp3T8+HGtXLnyjk6qBPKbOXPmqGjRog7LPvroIwUFBcnJyUnvv//+fenrdhw+fFg2m01xcXH3u5V8rVSpUnn6vzsjVsDfdOrUSZ06dbruOldXV61atSrX27zVVHGlSpW6bk3Tpk3VtGnTXL8fHlxD7+HRwffy6AyHaWlpGjBggCIjI9WhQwfZ7fb73RLggGAFAMg3jh49qkuXLqlly5aM3iJP4lAgAMASWVlZGj9+vMqUKSMXFxeVKFFC7777rqQrE+vabDaHSU/j4uJks9kc5nKbM2eOSpQoITc3N7Vr104nT550WFe5cmVJUunSpbO99qqMjAwNGDBAAQEBKly4sEqVKqWxY8ea6202m6ZPn65nn31Wrq6uCg4O1ldffeWwjT///FOdO3eWl5eXvL291aZNm2zv9emnnyokJESFCxdWhQoVNG3aNIf127ZtU/Xq1VW4cGHVrFkz2zQs1zvMuWTJEocTqEeOHKlq1app5syZCgoKkpubmzp27HjDyWOzsrL06KOPasaMGQ7Ld+zYIZvNpj/++MPcbokSJeTi4qLAwEBFRERcd3vXc/WQZnR0tOrWravChQurUqVKWrdunVmTmZmpnj17Kjg4WK6uripfvrw++OADh+10795dbdu21cSJExUQECBvb2/179/f4Yq85ORktWrVyvzvtGDBgmz9REZGqnLlynJ3d1dQUJD69euns2fPmuuPHDmiVq1aycvLS+7u7qpUqZKWL1+e4/3NLUascM/cy8MYuP/y6qEk3D3Dhw/XrFmzNHnyZNWvX1+JiYnat29fjl+/detW9ejRQ2PGjFH79u21cuVKjRgxwlzfuXNnBQUFqUmTJtq2bZuCgoJUvHjxbNuZMmWKli5dqi+//FIlSpRQQkKCEhISHGreeustjRs3Th988IHmzZunF154QaGhoQoJCdH58+f1zDPP6KmnntKGDRtUoEABjR49Wi1atNCuXbtUqFAhzZo1SyNGjFBUVJSqV6+unTt3qlevXnJ3d1e3bt107tw5hYWFqVGjRpo/f77i4+P16quv3tbnevDgQX355ZdatmyZ0tLS1LNnT/Xv3/+6IcPJyUnPP/+8FixYoL59+5rLFy5cqDp16qh06dL6+uuvNXnyZEVHR6tSpUpKSkrSL7/8kuu+hg4dqvfff18VK1ZUZGSkWrdurfj4eHl7e5sB78svv5SPj482bdqk3r17KyAgwOF0i59++kkBAQH66aefdPDgQXXu3FnVqlVTr169JF0JXwkJCfrxxx9VqFAhRUREmJN4/n2fp0yZolKlSik+Pl79+vXTsGHDzKDbv39/ZWRkaMOGDXJ3d9dvv/1mXjR0NxCsAAB37MyZM/rggw8UFRVlzsf22GOPqX79+jnexgcffKDmzZvrjTfekHTlbgObNm3SypUrJV05z/HqpKnFixe/4S2jjh49qrJly6p+/fqy2WzXvQ1Ux44dzalRRo0apTVr1mjq1KmaNm2aoqOj5eTkpI8//tgcPfr0009VtGhRrVu3Ts2aNdOoUaM0adIktW/fXpIUHBys3377TTNnzlS3bt20YMECZWZm6pNPPpGbm5sqVaqkY8eO6Z///GeOP4+rLl68qLlz55pXDU+dOlUtW7bUpEmTrvsZdO3aVZGRkTpy5IhKliyprKwsRUdH68033zQ/H39/fzVp0kQFCxZUiRIl9OSTT+a6rwEDBqhDhw6SpOnTp2vlypWaPXu2hg0bpoIFC+qdd94xa4ODg7Vp0yZ9+eWXDsHKy8tLUVFRcnZ2VoUKFdSyZUutXbtWvXr10oEDB7RixQpt2bLFnIpm9uzZCgkJcehj4MCBDu8zatQo/fOf/zSD1dGjR9WhQweH0c67iUOBAIA7tnfvXqWnp6tx48Z3tI06deo4LLv2eU50795dcXFxKl++vCIiIrR69epsNdd7n71790qSYmNjdfDgQXl4eKhIkSIqUqSIihUrposXL+rQoUM6ceKEEhIS1LNnT3N9kSJFNHr0aB06dMjcl6pVqzpM9Ho7+yJdmaLlaqi6up2srCzt37//uvXVq1dXhQoV9Pnnn0uS1q9fr+TkZDPQdOzYURcuXFDp0qXVq1cvLV68WJcvX851X3/fnwIFCqhmzZrmZyhJM2bMUM2aNVW8eHEVKVJEs2bN0tGjRx22UalSJYcZzgMCAswRqb1795rbvapChQrZDp/+9NNPatq0qR555BF5eHjoxRdf1MmTJ3Xu3DlJUkREhEaPHq169eppxIgR2rVrV673NTcIVgCAO3ar2yw5OV35ufn7FbDXzm59qytoc+rxxx9XfHy8Ro0apQsXLqhTp076v//7v1u+7uroVFZWlmrUqKG4uDiHx4EDB9SlSxdzkt5Zs2Y5rN+zZ4+2bNmS431xcnLKVpeTGb+v9nmzySy7du2qhQsXSrpyGLB58+by8fGRJAUFBWn//v368MMP5erqqn79+unpp5++o9nGr+3tyy+/1GuvvaYePXpo9erViouL00svvaSMjAyH+oIFC2Z7/dXP9+pnc7P9PHLkiJ577jmFhoZq0aJFio2N1Ycffijp/32WL7/8sv744w+Fh4dr9+7dqlmzpqZOnXrH+3ojBCsAwB0rW7asXF1dtXbt2uuuv3ouVGJiorns2vmcKlasaAaTq659nlOenp7q3LmzZs2apS+++EKLFi3SqVOnbrjdLVu2qEKFCpKuBLPff/9dvr6+KlOmjMPDbrfLz89PjzzyiP74449s669OBFyxYkX98ssvunDhwg3fs3jx4jpz5ow5snK9z0S6cijr+PHj5vPNmzfLyclJ5cqVu+H+d+nSRbt371ZsbKy+/vprde3a1WG9q6urWrdurSlTpmjdunXavHmzdu/efcPtXc/f9+fy5cuKjY01P8ONGzeqbt266tevn6pXr64yZcqYo3k5FRISosuXL+vnn382l+3fv9/hxP2ff/5Zly9f1qRJk1S7dm2VK1fO4bO6KigoSH379tU333yjwYMHa9asWbnqJTcIVgCAO1a4cGG9/vrrGjZsmD777DMdOnRIW7Zs0ezZsyVJZcqUUVBQkEaOHKkDBw7o+++/16RJkxy2ERERoZUrV2rChAk6cOCAoqKizPOrcuPqidn79u3TgQMH9NVXX8nf39/hENJXX32lTz75RAcOHNCIESO0bds2DRgwQNKV0R4fHx+1adNGGzduVHx8vNavX69XX31Vx44dk3TlqrqxY8fqgw8+0IEDB7R79259+umnioyMlHQl2Dg5Oalnz5767bfftHz5ck2cONGhz1q1asnNzU1vvvmmDh48qIULF2rOnDnX/Wy7deumX375RRs3blRERIQ6dep0w3PMpCvnGtWtW1c9e/bU5cuX1aZNG3PdnDlzNHv2bO3Zs0d//PGH5s2bJ1dXV/NctOHDh+vFF1+85ef84YcfavHixdq3b5/69++vlJQU9ejRQ9KV/94///yzVq1apQMHDuitt97S9u3bb7nNvytfvrxatGihXr16aevWrYqNjdXLL7/sMDr62GOP6fLly5o6daq5L9deETlw4ECtWrVK8fHx2rFjh3788cds52lZiWAFALDEW2+9pcGDB+vtt99WSEiIOnfubJ4vU7BgQX3++efat2+fqlatqvHjxzvcM1OSateurY8//lhTp05VtWrVtHr1av373//OdR9FihTR+PHjVbNmTT3xxBM6fPiwli9fbh6OlKR33nlH0dHRqlKliubOnasFCxaoYsWKkq7ciHfDhg0qUaKE2rdvr5CQEPXo0UMXLlyQp6enpCuHlz7++GNzCogGDRpozpw55ohVkSJFtGzZMv3222+qXr26/vWvf2n8+PEOfRYrVkzz58/X8uXLVblyZX3++ecaOXJktv0pU6aM2rdvr+eee07NmjVTaGhotqkdrqdr16765Zdf1L59e4cwUrRoUc2aNUv16tVTlSpVtHbtWi1btsy8MCAxMTHbuVDXM27cOI0fP15Vq1bVxo0b9e2335qHG/v27av27durc+fOqlWrlk6ePKl+/frdcpvX+vTTTxUUFKQGDRqoffv26t27t3x9fc311apVU2RkpMaPH6/Q0FAtWLDAYWoN6crUD/3791dISIhatGih8uXL5+jzu102w6qD2siRtLQ02e12paammn+gDwumW3i4MN3C7bl48aLi4+MVHByswoUL3+92Hkg2m02LFy9W27Zt73crtzRy5EgtWbIkT90G5/DhwwoODtbOnTtVrVq1+92OpW7295fT329GrAAAACxCsAIAALAIE4QCAB4q+ekMmJEjR173vKv76UY3jscVjFgBAABYhGAFAHkQIwLAvWfF3x3BCgDykKszUZ8/f/4+dwI8fK7+3V07I3xucI4VAOQhzs7OKlq0qDn/k5ub201v6QHgzhmGofPnzys5OVlFixZ1uH9hbhGsACCPuTqj9tVwBeDeKFq06E1ntM8JghUA5DE2m00BAQHy9fW15Ma4AG6tYMGCdzRSdRXBCgDyKGdnZ0u+6AHcO5y8DgAAYBGCFQAAgEUI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oaFmzebNm2W3281QJUm1a9eW3W53qAkNDTVDlSQ1b95c6enpDocmr5Wenq60tDSHBwAAeDDlOliVLl1aJ0+ezLb89OnTKl26tCVNXfXHH39o+vTpKlu2rFatWqW+ffsqIiJCn332mSQpKSlJkuTn5+fwOj8/P3NdUlKSChUqJC8vr5vW+Pr6Znt/X19fh5pr38fLy0uFChUya65n7Nix5nlbdrv9rpyHBgAA8oZcB6vDhw9f936A6enp+vPPPy1p6qqsrCw9/vjjGjNmjKpXr64+ffqoV69emj59ukOdzeZ4jMkwjGzLrnVtzfXqb6fmWsOHD1dqaqr5SEhIuGlfAAAg/8rxyetLly41/71q1SrZ7XbzeWZmptauXatSpUpZ2lxAQIAqVqzosCwkJESLFi2SJPn7+0u6MpoUEBBg1iQnJ5ujS/7+/srIyFBKSorDqFVycrLq1q1r1vz111/Z3v/EiRMO29m6davD+pSUFF26dCnbSNbfubi4yMXFJcf7DAAA8q8cB6u2bdtKujJq061bN4d1BQsWVKlSpTRp0iRLm6tXr57279/vsOzAgQMqWbKkJCk4OFj+/v5as2aNqlevLknKyMjQ+vXrNX78eElSjRo1VLBgQa1Zs0adOnWSJCUmJmrPnj2aMGGCJKlOnTpKTU3Vtm3b9OSTT0qStm7dqtTUVDN81alTR++++64SExPNELd69Wq5uLioRo0alu43AADIn3IcrLKysiRdCTPbt2+Xj4/PXWvqqtdee01169bVmDFj1KlTJ23btk0fffSRPvroI0lXQt7AgQM1ZswYlS1bVmXLltWYMWPk5uamLl26SJLsdrt69uypwYMHy9vbW8WKFdOQIUNUuXJlNWnSRNKVUbAWLVqoV69emjlzpqQr0y2EhYWpfPnykqRmzZqpYsWKCg8P13vvvadTp05pyJAh6tWr10N3dR8AALi+XM9jdS9nV3/iiSe0ePFiDR8+XP/5z38UHBys999/X127djVrhg0bpgsXLqhfv35KSUlRrVq1tHr1anMOK0maPHmyChQooE6dOunChQtq3Lix5syZY85hJUkLFixQRESEefVg69atFRUVZa53dnbW999/r379+qlevXpydXVVly5dNHHixHvwSQAAgPwg1/NYRUREqEyZMoqIiHBYHhUVpYMHD+r999+3sr8HDvNY4WHBPFYA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x3TgwIE77gmAVLFiRZ07d07SlWCUmZl5V96nQoUK2rVrl8P5XT///PNdeS/cHwQrPPQqV66srl27aurUqeayhg0b6sSJE5owYYIOHTqkDz/8UCtWrLjj9+ratat8fHzUpk0bbdy4UfHx8Vq/fr1effXVXJ1E36BBAz399NPq0KGD1qxZo/j4eK1YsUIrV6684x6BB9nJkyfVqFEjzZ8/X7t27VJ8fLy++uorTZgwQW3atJF05cq+tWvXKikpSSkpKZa+f5cuXZSVlaXevXtr7969WrVqlSZOnChJeXoePeQcwQqQNGrUKIfRqZCQEE2bNk0ffvihqlatqm3btmnIkCF3/D5ubm7asGGDSpQoofbt2yskJEQ9evTQhQsX5OnpmattLVq0SE888YReeOEFVaxYUcOGDbtr/y8beFAUKVJEtWrV0uTJk/X0008rNDRUb731lnr16qWoqChJV67qW7NmjYKCglS9enVL39/T01PLli1TXFycqlWrpn/96196++23JcnhvCvkXzaDg7sAANw3CxYs0EsvvaTU1FTOn3wAcPI6AAD30GeffabSpUvrkUce0S+//KLXX39dnTp1IlQ9IAhWAADcQ0lJSXr77beVlJSkgIAAdezYUe++++79bgsW4VAgAACARTh5HQAAwCIEKwAAAIsQrAAAACxCsAIAALAIwQoA7pN169bJZrPp9OnTd7Sd7t27q23btpb0BODOEKwAPHAaNmyogQMHZlu+ZMkSh9uGzJkzRzabTTabTc7OzvLy8lKtWrX0n//8R6mpqQ6v7d69u1n798fBgwev28P1QtPx48cVGhqq+vXr6/Tp06pbt64SExNzdZ9IAHkbwQrAQ83T01OJiYk6duyYNm3apN69e+uzzz5TtWrVdPz4cYfaFi1aKDEx0eERHByco/c5dOiQ6tevrxIlSmj16tUqWrSoChUqJH9/f+4RBzxACFYAHmo2m03+/v4KCAhQSEiIevbsqU2bNuns2bMaNmyYQ62Li4v8/f0dHs7Ozrd8j127dql+/fqqVauWvv32W7m5uUnKPqo1Z84cFS1aVKtWrVJISIiKFClihrmrMjMzNWjQIBUtWlTe3t4aNmyYmI4QyDsIVgBwDV9fX3Xt2lVLly694xtbb9q0SQ0aNFD79u21YMECFSxY8Kb158+f18SJEzVv3jxt2LBBR48edbgB+KRJk/TJJ59o9uzZiomJ0alTp7R48eI76hGAdQhWAHAdFSpU0JkzZ3Ty5Elz2XfffaciRYqYj44dO95yO+3atVOrVq304Ycfysnp1l+5ly5d0owZM1SzZk09/vjjGjBggNauXWuuf//99zV8+HB16NBBISEhmjFjBudoAXkI9woEgOu4enjt7+c/PfPMM5o+fbr53N3d/ZbbadOmjRYvXqyNGzfqqaeeumW9m5ubHnvsMfN5QECAkpOTJUmpqalKTExUnTp1zPUFChRQzZo1ORwI5BGMWAF44Hh6ema7qk+STp8+LU9PzxxtY+/evfL09JS3t7e5zN3dXWXKlDEfAQEBt9zOzJkz9cILL+jZZ5/V+vXrb1l/7aFCm81GaALyEYIVgAdOhQoV9PPPP2dbvn37dpUvX/6Wr09OTtbChQvVtm3bHB2+uxmbzaaZM2cqPDxczz33nNatW3fb27Lb7QoICNCWLVvMZZcvX1ZsbOwd9QjAOhwKBPDA6devn6KiotS/f3/17t1brq6uWrNmjWbPnq158+Y51BqGoaSkJBmGodOnT2vz5s0aM2aM7Ha7xo0bZ0k/NptN06ZNk7Ozs1q2bKlly5apUaNGt7WtV199VePGjVPZsmUVEhKiyMjIO55gFIB1CFYAHjilSpXSxo0b9a9//UvNmjXTxYsXVa5cOc2ZMyfbCedpaWkKCAiQzWaTp6enypcvr27duunVV1/N8WHDnLDZbIqKipKzs7PCwsK0dOlSFSiQ+6/gwYMHKzExUd27d5eTk5N69Oihdu3aXffQJ4B7z2Zw8B4AAMASnGMFAABgEYIVAACARQhWAAAAFiFYAQAAWIRgBQAAYBGCFQAAgEUIVgAAABYhWAEAAFiEYAUAAGARghUAAIBFCFYAAAAWIVgBAABY5P8Dsyef2/bBPVwAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "ax = performance_df.plot.bar(\n", + " color=\"#7400ff\",\n", + " ylim=(1, 100000),\n", + " rot=0,\n", + " xlabel=\"UDF Kind\",\n", + " ylabel=\"Speedup factor\",\n", + ")\n", + "ax.bar_label(ax.containers[0], fmt=\"%.0f\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## UDF Performance in GroupBy" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "size = 100_000_000\n", + "pdf = pd.DataFrame()\n", + "pdf[\"key\"] = np.random.randint(0, 2, size)\n", + "pdf[\"val\"] = np.random.randint(0, 7, size)\n", + "\n", + "\n", + "def custom_formula_udf(df):\n", + " df[\"out\"] = df[\"key\"] * df[\"val\"] - 10\n", + " return df\n", + "\n", + "\n", + "gdf = cudf.from_pandas(pdf)" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "pandas_udf_groupby, cudf_udf_groupby = timeit_pandas_cudf(\n", + " pdf,\n", + " gdf,\n", + " lambda df: df.groupby([\"key\"], group_keys=False).apply(custom_formula_udf),\n", + " number=10,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
cudf speedup vs. pandas
Grouped UDF423.83606
\n", + "
" + ], + "text/plain": [ + " cudf speedup vs. pandas\n", + "Grouped UDF 423.83606" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "performance_df = pd.DataFrame(\n", + " {\"cudf speedup vs. pandas\": [pandas_udf_groupby / cudf_udf_groupby]},\n", + " index=[\"Grouped UDF\"],\n", + ")\n", + "performance_df" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "image/png": "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4MCB6tWrl1599VXVrFlTfn5+GjZsmC5evGgfk5GRoe7du9v/nv79738XqicuLk7h4eHy8PBQcHCwhg4dqrNnz9r7jx8/ru7du6tatWry8PBQkyZN9PHHHxf5eJ1F2AEAC5s4caKmTZumSZMmad++fVqxYoUCAgKKvP1XX32lQYMGaejQoUpOTtb999+vKVOm2Pv79Omjzz//XNLlIJGWlqbg4OBC+5kzZ47Wrl2rVatW6cCBA1q+fLnq1q3rMGbSpEl6+OGH9c033+ivf/2rHnvsMe3fv1+SdP78ed1///3y9PTUF198oW3btsnT01NdunRRXl6eJGnRokX629/+ppdffln79+9XbGysJk2apCVLlkiSzp07p27duqlhw4ZKTExUTEyMxo4d69T3ecXhw4e1atUqffDBB/r000+VnJysYcOGXXVshQoV9OijjxYKBStWrFDbtm1Vr149rVmzRrNmzdKbb76pQ4cO6f3337eHSGeMGzdOY8aM0e7duxUZGakePXrYw+mV0LVq1Srt27dP//jHP/TCCy9o1apVDvvYtGmTjhw5ok2bNmnJkiWKj49XfHy8vX/gwIE6duyYNm7cqDVr1mjevHnKyMgodMxz5szRnj17tGTJEm3cuFHjx4+39w8bNky5ubn64osvlJKSomnTpsnT09Pp4y0yA5OVlWUkmaysrLIuBUA5lJOTY/bt22dycnIK9Y3VrX05Izs727i5uZlFixZdtX/Tpk1GksnMzLS37d6920gyqampxhhjHnvsMdOlSxeH7fr06WN8fHyuuc3VjBgxwvzpT38yBQUFV+2XZJ555hmHtoiICPPss88aY4xZvHixadiwocP2ubm5xt3d3Xz22WfGGGOCg4PNihUrHPbx0ksvmbZt2xpjjHnzzTeNr6+vOXfunL1//vz5RpLZvXu3McaYt956y+HYjDEmISHB/PrncvLkyaZixYrm5MmT9rZPPvnEVKhQwaSlpV31+JKSkozNZjPHjh0zxhhz6dIlU6tWLfPGG28YY4yZOXOmadCggcnLy7vq9jeSmppqJJlXXnnF3nbx4kVTu3ZtM23atGtuN3ToUPPwww/b3w8YMMDUqVPH5Ofn29seeeQR06dPH2OMMQcOHDCSzI4dO+z9+/fvN5LMrFmzrvk5q1atMn5+fvb34eHhJiYmpkjHdr1/f0X9/WZmBwAsav/+/crNzVWHDh2KtY+2bds6tP32fVEMHDhQycnJatiwoUaOHKl169YVGnO1z7kys5OYmKjDhw/Ly8tLnp6e8vT0lK+vry5cuKAjR47ohx9+0MmTJzV48GB7v6enp6ZMmaIjR47Yj6V58+YOd8G+mWORpJCQENWuXdthPwUFBTpw4MBVx7ds2VKNGjXS22+/LUnasmWLMjIy1Lt3b0nSI488opycHNWrV09PPfWUEhISlJ+f73Rdvz4eFxcXtW7d2v4dStKCBQvUunVr+fv7y9PTU4sWLdKJEycc9tGkSRNVrFjR/r5mzZr2mZv9+/fb93tFo0aNCp3627Rpk6KiolSrVi15eXmpf//++umnn3Tu3DlJ0siRIzVlyhTdc889mjx5sr799lunj9UZhB0AsCh3d/fr9l+5M60x/3c9+6/XZvy2rzh+//vfKzU1VS+99JJycnLUu3dv/eUvf7nhdlfWyhQUFKhVq1ZKTk52eB08eFB9+/ZVQUGBpMunsn7dv2fPHu3YsaPIx1KhQoVC4377nVyvzus9M61fv35asWKFpMunsDp37qzq1atLkoKDg3XgwAG98cYbcnd319ChQ3XfffcV6bOLWtuqVav03HPPadCgQVq3bp2Sk5P1xBNP2E8DXlGpUqVC21/5fq98N9c7zuPHj+vPf/6zmjZtqnfffVeJiYl64403JP3fd/nkk0/q6NGjevzxx5WSkqLWrVvr9ddfL/axXgthBwAsKiwsTO7u7tqwYcNV+/39/SVJaWlp9rbk5GSHMb/73e/sYeGK374vKm9vb/Xp00eLFi3SO++8o3fffVc///zzNfe7Y8cONWrUSNLlsHTo0CHVqFFD9evXd3j5+PgoICBAtWrV0tGjRwv1h4aG2o/lm2++UU5OzjU/09/fX2fOnLHPQFztO5EuX4F2+vRp+/svv/xSFSpUUIMGDa55/H379lVKSooSExO1Zs0a9evXz6Hf3d1dPXr00Jw5c7R582Z9+eWXSklJueb+rubXx5Ofn6/ExET7d7h161ZFRkZq6NChatmyperXr2+f9Sqqxo0bKz8/X7t27bK3HThwwGFx9q5du5Sfn6+ZM2eqTZs2atCggcN3dUVwcLCeeeYZvffeexozZowWLVrkVC3OIOwAgEVVrlxZEyZM0Pjx47V06VIdOXJEO3bs0OLFiyVJ9evXV3BwsGJiYnTw4EF99NFHmjlzpsM+Ro4cqU8//VTTp0/XwYMHNXfuXH366adO1zJr1iytXLlS3333nQ4ePKjVq1crMDDQ4fTH6tWr9a9//UsHDx7U5MmT9fXXX2v48OGSLs+KVK9eXT179tTWrVuVmpqqLVu2aNSoUTp16pSky1czTZ06VbNnz9bBgweVkpKit956S3FxcZIuh40KFSpo8ODB2rdvnz7++GO9+uqrDnVGRESoSpUqeuGFF3T48GGtWLHCYXHur7/bAQMG6JtvvtHWrVs1cuRI9e7dW4GBgdf8DkJDQxUZGanBgwcrPz9fPXv2tPfFx8dr8eLF2rNnj44ePaply5bJ3d1dderUkXR5oXn//v1v+D2/8cYbSkhI0Hfffadhw4YpMzNTgwYNknT573vXrl367LPPdPDgQU2aNEk7d+684T5/rWHDhurSpYueeuopffXVV0pMTNSTTz7pMIt41113KT8/X6+//rr9WH57JVp0dLQ+++wzpaamKikpSRs3blTjxo2dqsUpRVodZHEsUAZwPddbIFneXbp0yUyZMsXUqVPHVKpUyYSEhJjY2Fh7/7Zt20x4eLipXLmy+eMf/2hWr15daLHx4sWLTe3atY27u7vp3r27efXVV51eoLxw4ULTokUL4+HhYby9vU2HDh1MUlKSvV+SeeONN0xUVJRxc3MzderUMW+//bbDPtLS0kz//v1N9erVjZubm6lXr5556qmnHP7b/e9//9u0aNHCuLq6mmrVqpn77rvPvPfee/b+L7/80jRv3ty4urqaFi1amHfffddhgbIxlxck169f31SuXNl069bNLFy4sNAC5ebNm5t58+aZoKAgU7lyZfPQQw+Zn3/++YZ/H2+88YaRZPr37+/QnpCQYCIiIoy3t7fx8PAwbdq0MZ9//rm9f8CAAaZdu3bX3O+VBcorVqwwERERxtXV1TRu3Nhs2LDBPubChQtm4MCBxsfHx1StWtU8++yz5vnnnzfNmzd3+JyePXs67HvUqFEOn52WlmYeeOAB4+bmZkJCQszSpUtNnTp1HBYox8XFmZo1axp3d3fTuXNns3TpUofF8MOHDzd33XWXcXNzM/7+/ubxxx83P/7441WPrSQWKNuMKaETsrex7Oxs+fj4KCsrS97e3mVdDoBy5sKFC0pNTVVoaKgqV65c1uVYks1mU0JCgnr16lXWpdxQTEyM3n///aue3iorVn7sxfX+/RX195vTWAAAwNIIOwAAwNI4jSVOYwG4Pk5jAWWH01gAAAA3QNgBgCJiIhy49Uri3x1hBwBu4ModZc+fP1/GlQB3niv/7n57Z2dnuJRUMQBgVRUrVlTVqlXtzweqUqXKdW+XD6D4jDE6f/68MjIyVLVqVYfndTmLsAMARXDlzrhXAg+AW6Nq1arXvTN1URB2AKAIbDabatasqRo1apTIwxkB3FilSpWKNaNzBWEHAJxQsWLFEvmPL4BbhwXKAADA0gg7AADA0gg7AADA0gg7AADA0gg7AADA0gg7AADA0gg7AADA0gg7AADA0gg7AADA0gg7AG5rU6dOlc1mU3R0tCTp4sWLmjBhgsLDw+Xh4aGgoCD1799fp0+fvur2xhh17dpVNptN77///q0rHMAtQ9gBcNvauXOnFi5cqGbNmtnbzp8/r6SkJE2aNElJSUl67733dPDgQfXo0eOq+3jttdd4gjlgcTwbC8Bt6ezZs+rXr58WLVqkKVOm2Nt9fHy0fv16h7Gvv/667r77bp04cUIhISH29m+++UZxcXHauXOnatasectqB3BrMbMD4LY0bNgwPfDAA+rYseMNx2ZlZclms6lq1ar2tvPnz+uxxx7T3LlzFRgYWIqVAihrzOwAuO2sXLlSSUlJ2rlz5w3HXrhwQc8//7z69u0rb29ve/tzzz2nyMhI9ezZszRLBVAOEHYA3FZOnjypUaNGad26dapcufJ1x168eFGPPvqoCgoKNG/ePHv72rVrtXHjRu3evbu0ywVQDnAaC8BtJTExURkZGWrVqpVcXFzk4uKiLVu2aM6cOXJxcdGlS5ckXQ46vXv3VmpqqtavX+8wq7Nx40YdOXJEVatWte9Dkh5++GG1b9++LA4LQCmyGWNMWRdR1rKzs+Xj46OsrCyH/yACKH/OnDmj48ePO7Q98cQTatSokSZMmKCmTZvag86hQ4e0adMm+fv7O4xPT0/Xjz/+6NAWHh6u2bNnq3v37goNDS314wBQfEX9/eY0FoDbipeXl5o2berQ5uHhIT8/PzVt2lT5+fn6y1/+oqSkJH344Ye6dOmS0tPTJUm+vr5ydXVVYGDgVRclh4SEEHQACyLsALCUU6dOae3atZKkFi1aOPRt2rSJ01TAHYiwA+C2t3nzZvuf69atq5s5O88ZfcC6WKAMAAAsjZkd3JHG8XQAwLJmMEmH32BmBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWFq5CTtTp06VzWZTdHS0vc0Yo5iYGAUFBcnd3V3t27fX3r17HbbLzc3ViBEjVL16dXl4eKhHjx46derULa4eAACUV+Ui7OzcuVMLFy5Us2bNHNqnT5+uuLg4zZ07Vzt37lRgYKCioqJ05swZ+5jo6GglJCRo5cqV2rZtm86ePatu3brp0qVLt/owAABAOVTmYefs2bPq16+fFi1apGrVqtnbjTF67bXX9Le//U0PPfSQmjZtqiVLluj8+fNasWKFJCkrK0uLFy/WzJkz1bFjR7Vs2VLLly9XSkqKPv/887I6JAAAUI6UedgZNmyYHnjgAXXs2NGhPTU1Venp6erUqZO9zc3NTe3atdP27dslSYmJibp48aLDmKCgIDVt2tQ+5mpyc3OVnZ3t8AIAANbkUpYfvnLlSiUlJWnnzp2F+tLT0yVJAQEBDu0BAQE6fvy4fYyrq6vDjNCVMVe2v5qpU6fqxRdfLG75AADgNlBmMzsnT57UqFGjtHz5clWuXPma42w2m8N7Y0yhtt+60ZiJEycqKyvL/jp58qRzxQMAgNtGmYWdxMREZWRkqFWrVnJxcZGLi4u2bNmiOXPmyMXFxT6j89sZmoyMDHtfYGCg8vLylJmZec0xV+Pm5iZvb2+HFwAAsKYyCzsdOnRQSkqKkpOT7a/WrVurX79+Sk5OVr169RQYGKj169fbt8nLy9OWLVsUGRkpSWrVqpUqVarkMCYtLU179uyxjwEAAHe2Mluz4+XlpaZNmzq0eXh4yM/Pz94eHR2t2NhYhYWFKSwsTLGxsapSpYr69u0rSfLx8dHgwYM1ZswY+fn5ydfXV2PHjlV4eHihBc8AAODOVKYLlG9k/PjxysnJ0dChQ5WZmamIiAitW7dOXl5e9jGzZs2Si4uLevfurZycHHXo0EHx8fGqWLFiGVYOAADKC5sxxpR1EWUtOztbPj4+ysrKYv3OHWLc9de4A7iNzbjjf9XuHEX9/S7z++wAAACUJsIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNMIOAACwNKfCTn5+vl588UWdPHmytOoBAAAoUU6FHRcXF82YMUOXLl0qrXoAAABKlNOnsTp27KjNmzeXQikAAAAlz8XZDbp27aqJEydqz549atWqlTw8PBz6e/ToUWLFAQAAFJfNGGOc2aBChWtPBtlsttvyFFd2drZ8fHyUlZUlb2/vsi4Ht8A4W1lXAKC0zHDqVw23s6L+fjs9s1NQUFCswgAAAG4lLj0HAACWdlNhZ8uWLerevbvq16+vsLAw9ejRQ1u3bi3p2gAAAIrN6bCzfPlydezYUVWqVNHIkSM1fPhwubu7q0OHDlqxYkVp1AgAAHDTnF6g3LhxYz399NN67rnnHNrj4uK0aNEi7d+/v0QLvBVYoHznYYEyYF0sUL5zFPX32+mZnaNHj6p79+6F2nv06KHU1FRndwcAAFCqnA47wcHB2rBhQ6H2DRs2KDg42Kl9zZ8/X82aNZO3t7e8vb3Vtm1bffLJJ/Z+Y4xiYmIUFBQkd3d3tW/fXnv37nXYR25urkaMGKHq1avLw8NDPXr00KlTp5w9LAAAYFFOX3o+ZswYjRw5UsnJyYqMjJTNZtO2bdsUHx+v2bNnO7Wv2rVr65VXXlH9+vUlSUuWLFHPnj21e/duNWnSRNOnT1dcXJzi4+PVoEEDTZkyRVFRUTpw4IC8vLwkSdHR0frggw+0cuVK+fn5acyYMerWrZsSExNVsWJFZw8PAABYjNNrdiQpISFBM2fOtK/Pady4scaNG6eePXsWuyBfX1/NmDFDgwYNUlBQkKKjozVhwgRJl2dxAgICNG3aNA0ZMkRZWVny9/fXsmXL1KdPH0nS6dOnFRwcrI8//lidO3cu0meyZufOw5odwLpYs3PnKLWbCkrSgw8+qAcffPCmi7uaS5cuafXq1Tp37pzatm2r1NRUpaenq1OnTvYxbm5uateunbZv364hQ4YoMTFRFy9edBgTFBSkpk2bavv27dcMO7m5ucrNzbW/z87OLtFjAQAA5YfTa3bq1aunn376qVD7L7/8onr16jldQEpKijw9PeXm5qZnnnlGCQkJ+t3vfqf09HRJUkBAgMP4gIAAe196erpcXV1VrVq1a465mqlTp8rHx8f+cnatEQAAuH04HXaOHTt21edf5ebm6vvvv3e6gIYNGyo5OVk7duzQs88+qwEDBmjfvn32fpvN8XyDMaZQ22/daMzEiROVlZVlf508edLpugEAwO2hyKex1q5da//zZ599Jh8fH/v7S5cuacOGDapbt67TBbi6utoXKLdu3Vo7d+7U7Nmz7et00tPTVbNmTfv4jIwM+2xPYGCg8vLylJmZ6TC7k5GRocjIyGt+ppubm9zc3JyuFQAA3H6KHHZ69eol6fJMy4ABAxz6KlWqpLp162rmzJnFLsgYo9zcXIWGhiowMFDr169Xy5YtJUl5eXnasmWLpk2bJklq1aqVKlWqpPXr16t3796SpLS0NO3Zs0fTp08vdi0AAOD2V+Swc+Vp56Ghodq5c6eqV69e7A9/4YUX1LVrVwUHB+vMmTNauXKlNm/erE8//VQ2m03R0dGKjY1VWFiYwsLCFBsbqypVqqhv376SJB8fHw0ePFhjxoyRn5+ffH19NXbsWIWHh6tjx47Frg8AANz+nL4aqyTvkvy///1Pjz/+uNLS0uTj46NmzZrp008/VVRUlCRp/PjxysnJ0dChQ5WZmamIiAitW7fOfo8dSZo1a5ZcXFzUu3dv5eTkqEOHDoqPj+ceOwAAQNJN3Gdn5MiRql+/vkaOHOnQPnfuXB0+fFivvfZaSdZ3S3CfnTsP99kBrIv77Nw5Su3ZWO+++67uueeeQu2RkZFas2aNs7sDAAAoVU6HnZ9++snhSqwrvL299eOPP5ZIUQAAACXF6bBTv359ffrpp4XaP/nkk5u6qSAAAEBpcnqB8ujRozV8+HD98MMP+tOf/iTp8hPPZ86ceVuu1wEAANbmdNgZNGiQcnNz9fLLL+ull16SJNWtW1fz589X//79S7xAAACA4ripp55f8cMPP8jd3V2enp4lWdMtx9VYdx6uxgKsi6ux7hyl+tTzK/z9/YuzOQAAQKm7qbCzZs0arVq1SidOnFBeXp5DX1JSUokUBgAAUBKcvhprzpw5euKJJ1SjRg3t3r1bd999t/z8/HT06FF17dq1NGoEAAC4aU6HnXnz5mnhwoWaO3euXF1dNX78eK1fv14jR45UVlZWadQIAABw05wOOydOnFBkZKQkyd3dXWfOnJEkPf7443r77bdLtjoAAIBicjrsBAYG6qeffpIk1alTRzt27JB0+QGhxbiwCwAAoFQ4HXb+9Kc/6YMPPpAkDR48WM8995yioqLUp08fPfjggyVeIAAAQHE4fTXWwoULVVBQIEl65pln5Ovrq23btql79+565plnSrxAAACA4ijSzM5DDz2k7OxsSdLy5ct16dIle1/v3r01Z84cjRw5Uq6urqVTJQAAwE0qUtj58MMPde7cOUnSE088wVVXAADgtlGk01iNGjXSxIkTdf/998sYo1WrVl3ztsw8HwsAAJQnRXo21vbt2zV69GgdOXJEP//8s7y8vGSzFX64kM1m088//1wqhZYmno115+HZWIB18WysO0eJPhsrMjLSfol5hQoVdPDgQdWoUaNkKgUAAChFTl96npqaygNAAQDAbcPpS8/r1KlTGnUAAACUCqdndgAAAG4nhB0AAGBphB0AAGBpTq/ZuSIjI0MHDhyQzWZTgwYNuDoLAACUS07P7GRnZ+vxxx9XrVq11K5dO913332qVauW/vrXv3JnZQAAUO44HXaefPJJffXVV/rwww/1yy+/KCsrSx9++KF27dqlp556qjRqBAAAuGlOn8b66KOP9Nlnn+nee++1t3Xu3FmLFi1Sly5dSrQ4AACA4nJ6ZsfPz08+Pj6F2n18fFStWrUSKQoAAKCkOB12/v73v2v06NFKS0uzt6Wnp2vcuHGaNGlSiRYHAABQXE6fxpo/f74OHz6sOnXqKCQkRJJ04sQJubm56YcfftCbb75pH5uUlFRylQIAANwEp8NOr169SqEMAACA0uF02Jk8eXJp1AEAAFAquIMyAACwNKdndipUqCCbzXbN/kuXLhWrIAAAgJLkdNhJSEhweH/x4kXt3r1bS5Ys0YsvvlhihQEAAJQEp8NOz549C7X95S9/UZMmTfTOO+9o8ODBJVIYAABASSixNTsRERH6/PPPS2p3AAAAJaJEwk5OTo5ef/111a5duyR2BwAAUGKcPo1VrVo1hwXKxhidOXNGVapU0fLly0u0OAAAgOJyOuzMmjXLIexUqFBB/v7+ioiI4NlYAACg3HE67AwcOLAUygAAACgdRQo73377bZF32KxZs5suBgAAoKQVKey0aNFCNptNxhhJ4qaCAADgtlGkq7FSU1N19OhRpaam6r333lNoaKjmzZun3bt3a/fu3Zo3b57uuusuvfvuu6VdLwAAgFOKNLNTp04d+58feeQRzZkzR3/+85/tbc2aNVNwcLAmTZrEU9EBAEC54vR9dlJSUhQaGlqoPTQ0VPv27SuRogAAAEqK02GncePGmjJlii5cuGBvy83N1ZQpU9S4ceMSLQ4AAKC4nL70fMGCBerevbuCg4PVvHlzSdI333wjm82mDz/8sMQLBAAAKA6nw87dd9+t1NRULV++XN99952MMerTp4/69u0rDw+P0qgRAADgpjkddiSpSpUqevrpp0u6FgAAgBJ3Uw8CXbZsme69914FBQXp+PHjki4/RuI///lPiRYHAABQXE6Hnfnz52v06NHq2rWrMjMz7TcRrFatml577bWSrg8AAKBYnA47r7/+uhYtWqS//e1vcnH5v7NgrVu3VkpKSokWBwAAUFxOh53U1FS1bNmyULubm5vOnTtXIkUBAACUFKfDTmhoqJKTkwu1f/LJJ/rd735XEjUBAACUGKevxho3bpyGDRumCxcuyBijr7/+Wm+//bamTp2qf/7zn6VRIwAAwE1zOuw88cQTys/P1/jx43X+/Hn17dtXtWrV0uzZs/Xoo4+WRo0AAAA3zWaMMTe78Y8//qiCggLVqFGjJGu65bKzs+Xj46OsrCx5e3uXdTm4BcbZyroCAKVlxk3/quF2U9Tf75u6z05+fr4+//xzvfvuu3J3d5cknT59WmfPnr25agEAAEqJ06exjh8/ri5duujEiRPKzc1VVFSUvLy8NH36dF24cEELFiwojToBAABuitMzO6NGjVLr1q2VmZlpn9WRpAcffFAbNmwo0eIAAACKy+mZnW3btum///2vXF1dHdrr1Kmj77//vsQKAwAAKAlOz+wUFBTYHxHxa6dOnZKXl5dT+5o6dar+8Ic/yMvLSzVq1FCvXr104MABhzHGGMXExCgoKEju7u5q37699u7d6zAmNzdXI0aMUPXq1eXh4aEePXro1KlTzh4aAACwIKfDTlRUlMMzsGw2m86ePavJ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MmSIsrKy5O/vr2XLlqlPnz6SpNOnTys4OFgff/yxOnfuXKTPzs7Olo+Pj7KysuTt7V0qx4fyZZytrCsAUFpmlItfNdwKRf39LrdrdlJTU5Wenq5OnTrZ29zc3NSuXTtt375dkpSYmKiLFy86jAkKClLTpk3tY64mNzdX2dnZDi8AAGBN5TbspKenS5ICAgIc2gMCAux96enpcnV1VbVq1a455mqmTp0qHx8f+ys4OLiEqwcAAOVFuQ07V9hsjucbjDGF2n7rRmMmTpyorKws++vkyZMlUisAACh/ym3YCQwMlKRCMzQZGRn22Z7AwEDl5eUpMzPzmmOuxs3NTd7e3g4vAABgTeU27ISGhiowMFDr16+3t+Xl5WnLli2KjIyUJLVq1UqVKlVyGJOWlqY9e/bYxwAAgDubS1l++NmzZ3X48GH7+9TUVCUnJ8vX11chISGKjo5WbGyswsLCFBYWptjYWFWpUkV9+/aVJPn4+Gjw4MEaM2aM/Pz85Ovrq7Fjxyo8PFwdO3Ysq8MCAADlSJmGnV27dun++++3vx89erQkacCAAYqPj9f48eOVk5OjoUOHKjMzUxEREVq3bp28vLzs28yaNUsuLi7q3bu3cnJy1KFDB8XHx6tixYq3/HgAAED5U27us1OWuM/OnYf77ADWxX127hy3/X12AAAASgJhBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWBphBwAAWJpLWRdQ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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "ax = performance_df.plot.bar(\n", + " color=\"#7400ff\", ylim=(1, 500), rot=0, ylabel=\"Speedup factor\"\n", + ")\n", + "ax.bar_label(ax.containers[0], fmt=\"%.0f\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# System Configuration" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## CPU Configuration" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Architecture: x86_64\n", + "CPU op-mode(s): 32-bit, 64-bit\n", + "Byte Order: Little Endian\n", + "Address sizes: 46 bits physical, 48 bits virtual\n", + "CPU(s): 80\n", + "On-line CPU(s) list: 0-79\n", + "Thread(s) per core: 2\n", + "Core(s) per socket: 20\n", + "Socket(s): 2\n", + "NUMA node(s): 2\n", + "Vendor ID: GenuineIntel\n", + "CPU family: 6\n", + "Model: 85\n", + "Model name: Intel(R) Xeon(R) Gold 6230 CPU @ 2.10GHz\n", + "Stepping: 7\n", + "CPU MHz: 800.049\n", + "CPU max MHz: 3900.0000\n", + "CPU min MHz: 800.0000\n", + "BogoMIPS: 4200.00\n", + "Virtualization: VT-x\n", + "L1d cache: 1.3 MiB\n", + "L1i cache: 1.3 MiB\n", + "L2 cache: 40 MiB\n", + "L3 cache: 55 MiB\n", + "NUMA node0 CPU(s): 0-19,40-59\n", + "NUMA node1 CPU(s): 20-39,60-79\n", + "Vulnerability Itlb multihit: KVM: Mitigation: Split huge pages\n", + "Vulnerability L1tf: Not affected\n", + "Vulnerability Mds: Not affected\n", + "Vulnerability Meltdown: Not affected\n", + "Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled v\n", + " ia prctl and seccomp\n", + "Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user\n", + " pointer sanitization\n", + "Vulnerability Spectre v2: Mitigation; Enhanced IBRS, IBPB conditional, RS\n", + " B filling\n", + "Vulnerability Srbds: Not affected\n", + "Vulnerability Tsx async abort: Mitigation; TSX disabled\n", + "Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtr\n", + " r pge mca cmov pat pse36 clflush dts acpi mmx f\n", + " xsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rd\n", + " tscp lm constant_tsc art arch_perfmon pebs bts \n", + " rep_good nopl xtopology nonstop_tsc cpuid aperf\n", + " mperf pni pclmulqdq dtes64 monitor ds_cpl vmx s\n", + " mx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid d\n", + " ca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadli\n", + " ne_timer aes xsave avx f16c rdrand lahf_lm abm \n", + " 3dnowprefetch cpuid_fault epb cat_l3 cdp_l3 inv\n", + " pcid_single intel_ppin ssbd mba ibrs ibpb stibp\n", + " ibrs_enhanced tpr_shadow vnmi flexpriority ept\n", + " vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep\n", + " bmi2 erms invpcid cqm mpx rdt_a avx512f avx512\n", + " dq rdseed adx smap clflushopt clwb intel_pt avx\n", + " 512cd avx512bw avx512vl xsaveopt xsavec xgetbv1\n", + " xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm\n", + " _mbm_local dtherm ida arat pln pts hwp hwp_act_\n", + " window hwp_epp hwp_pkg_req pku ospke avx512_vnn\n", + " i md_clear flush_l1d arch_capabilities\n" + ] + } + ], + "source": [ + "!lscpu" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## GPU Configuration" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mon Feb 6 17:43:52 2023 \n", + "+-----------------------------------------------------------------------------+\n", + "| NVIDIA-SMI 525.60.04 Driver Version: 525.60.04 CUDA Version: 12.0 |\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", + "| | | MIG M. |\n", + "|===============================+======================+======================|\n", + "| 0 H100 80GB HBM2e On | 00000000:1E:00.0 Off | 0 |\n", + "| N/A 30C P0 60W / 700W | 0MiB / 81559MiB | 0% Default |\n", + "| | | Disabled |\n", + "+-------------------------------+----------------------+----------------------+\n", + "| 1 H100 80GB HBM2e On | 00000000:22:00.0 Off | 0 |\n", + "| N/A 30C P0 60W / 700W | 0MiB / 81559MiB | 0% Default |\n", + "| | | Disabled |\n", + "+-------------------------------+----------------------+----------------------+\n", + " \n", + "+-----------------------------------------------------------------------------+\n", + "| Processes: |\n", + "| GPU GI CI PID Type Process name GPU Memory |\n", + "| ID ID Usage |\n", + "|=============================================================================|\n", + "| No running processes found |\n", + "+-----------------------------------------------------------------------------+\n" + ] + } + ], + "source": [ + "!nvidia-smi" + ] + } + ], + "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.10.9" + }, + "vscode": { + "interpreter": { + "hash": "b4f3463dcc83b00b9c65791e378b11fabec52613a2a7831cd4af76c548ff6047" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/performance_comparisons.ipynb b/notebooks/performance_comparisons.ipynb new file mode 120000 index 00000000000..68c8aa19eee --- /dev/null +++ b/notebooks/performance_comparisons.ipynb @@ -0,0 +1 @@ +../docs/cudf/source/user_guide/performance_comparisons.ipynb \ No newline at end of file