{ "metadata": { "name": "", "signature": "sha256:1058d7263dc37160a6c7f80057f634cc1fc2977e02c5ec0d774292ca4d875387" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Bquery - groupby on-disk\n", "\n", "In this notebook we will compare the performance several groupby solutions over out-of-core (disk-based) bcolz files against Python's golden standard: the fast, in-memory Pandas framework.\n", "\n", "Our goal is to show how with bcolz you can approach (and in some cases even exceed) the Pandas in-memory performance while working with solutions that only have the intermediate and/or end result in-memory.\n", "\n", "We will have two test cases: \n", "1) a query with a single groupby column and a single aggregated (sum) column\n", "2) a query with five groupby columns and three aggregated (sum) columns\n", "\n", "This is a simple example with 1 million rows, but you can experiment with other sizes to see how they affect the outcome.\n", "\n", "This performance comparison was run on a 16gb ram, 8 core, SSD-based DigitalOcean server on Ubuntu 14.04. System caching might influence the results, but in our experience this very much resembles our real-life experience. We encourage anyone to have their own test and share them with us.\n", "\n", "For the impatient, you can scroll to the end to see a graphical presentation of the performance results.\n", "\n", "The bquery framework provides methods to perform query and aggregation operations on bcolz containers, as well as accelerate these operations by pre-processing possible groupby columns.\n", "\n", "Bcolz is a light weight package that provides columnar, chunked data containers that can be compressed either in-memory and on-disk. that are compressed by default not only for reducing memory/disk storage, but also to improve I/O speed. It excels at storing and sequentially accessing large, numerical data sets. \n", "\n", "The code you'll find below was inspired on the following nicely written notebooks: \n", "Blaze - http://nbviewer.ipython.org/url/blaze.pydata.org/notebooks/timings-bcolz.ipynb \n", "Bcolz - http://nbviewer.ipython.org/github/Blosc/movielens-bench/blob/master/querying-ep14.ipynb" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%matplotlib inline" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 1 }, { "cell_type": "code", "collapsed": false, "input": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import itertools as itt\n", "import time\n", "import shutil\n", "import os\n", "import contextlib\n", "import pandas as pd\n", "import blaze as blz\n", "import bquery\n", "import cytoolz\n", "from cytoolz.curried import pluck as cytoolz_pluck\n", "from collections import OrderedDict\n", "import copy\n", "from prettyprint import pp" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 2 }, { "cell_type": "code", "collapsed": false, "input": [ "elapsed_times = OrderedDict()\n", "\n", "@contextlib.contextmanager\n", "def ctime(message=None):\n", " \"Counts the time spent in some context\"\n", " assert message is not None\n", " global elapsed_times\n", " t_elapsed = 0.0\n", " print('\\n')\n", " t = time.time()\n", " yield\n", " if message:\n", " print message + \": \", \n", " t_elapsed = time.time() - t\n", " print round(t_elapsed, 4), \"sec\"\n", " elapsed_times[message] = t_elapsed\n", "\n", "\n", "ga = itt.cycle(['ES', 'NL'])\n", "gb = itt.cycle(['b1', 'b2', 'b3', 'b4', 'b5'])\n", "gx = itt.cycle([1, 2])\n", "gy = itt.cycle([-1, -2])\n", "rootdir = 'bench-data.bcolz'\n", "if os.path.exists(rootdir):\n", " shutil.rmtree(rootdir)\n", "\n", "n_rows = 1000000\n", "\n", "# -- data\n", "z = np.fromiter(((a, b, x, y) for a, b, x, y in itt.izip(ga, gb, gx, gy)),\n", " dtype='S2,S2,i8,i8', count=n_rows)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 3 }, { "cell_type": "code", "collapsed": false, "input": [ "ct = bquery.ctable(z, rootdir=rootdir)\n", "ct.flush()" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 4 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### pandas" ] }, { "cell_type": "code", "collapsed": false, "input": [ "print('Simple Test Case')\n", "df = pd.DataFrame(z)\n", "with ctime(message='pandas'):\n", " result = df.groupby(['f0'], sort=False, as_index=False)['f2'].sum()\n", "# print(result)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Simple Test Case\n", "\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "pandas: " ] }, { "output_type": "stream", "stream": "stdout", "text": [ " 0.107 sec\n" ] } ], "prompt_number": 5 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### blaze" ] }, { "cell_type": "code", "collapsed": false, "input": [ "print('Simple Test Case')\n", "blaze_data = blz.Data(ct.rootdir)\n", "expr = blz.by(blaze_data.f0, sum_f2=blaze_data.f2.sum())\n", "with ctime(message='blaze (pandas + bcolz)'):\n", " result = blz.compute(expr)\n", "# print result" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Simple Test Case\n", "\n", "\n", "blaze (pandas + bcolz): " ] }, { "output_type": "stream", "stream": "stdout", "text": [ " 0.3613 sec\n" ] } ], "prompt_number": 6 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### bquery without caching" ] }, { "cell_type": "code", "collapsed": false, "input": [ "print('Simple Test Case')\n", "with ctime(message='bquery + bcolz'):\n", " result = ct.groupby(['f0'], ['f2'])\n", "# print(result)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Simple Test Case\n", "\n", "\n", "bquery + bcolz: " ] }, { "output_type": "stream", "stream": "stdout", "text": [ " 0.3155 sec\n" ] } ], "prompt_number": 7 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### bquery with caching" ] }, { "cell_type": "code", "collapsed": false, "input": [ "print('Simple Test Case')\n", "with ctime(message='bquery, create factorization cache'):\n", " ct.cache_factor(['f0'], refresh=True)\n", "\n", "with ctime(message='bquery + bcolz (fact. cached)'):\n", " result = ct.groupby(['f0'], ['f2'])\n", "# print(result)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Simple Test Case\n", "\n", "\n", "bquery, create factorization cache: " ] }, { "output_type": "stream", "stream": "stdout", "text": [ " 0.2038 sec\n", "\n", "\n", "bquery + bcolz (fact. cached): " ] }, { "output_type": "stream", "stream": "stdout", "text": [ " 0.2247 sec\n" ] } ], "prompt_number": 8 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Running Times Summary" ] }, { "cell_type": "code", "collapsed": false, "input": [ "print('Simple Test Case Running Time')\n", "elapsed_times_bak = OrderedDict({ k: v for (k,v) in sorted(elapsed_times.iteritems())})\n", "pp(elapsed_times_bak)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Simple Test Case Running Time\n", "{\n", " \"blaze (pandas + bcolz)\": 0.36127805709838867, \n", " \"bquery + bcolz\": 0.31554603576660156, \n", " \"bquery + bcolz (fact. cached)\": 0.22467803955078125, \n", " \"bquery, create factorization cache\": 0.20379304885864258, \n", " \"pandas\": 0.10704803466796875\n", "}\n" ] } ], "prompt_number": 9 }, { "cell_type": "code", "collapsed": false, "input": [ "print('Simple Test Case Running Time relative to Pandas')\n", "elapsed_times_bak = OrderedDict({ k: v for (k,v) in sorted(elapsed_times.iteritems())})\n", "pp(elapsed_times_bak)\n", "elapsed_times = elapsed_times_bak\n", "elapsed_times_norm = OrderedDict({ k: v/elapsed_times['pandas'] for (k,v) in sorted(elapsed_times.iteritems())})\n", "print '\\nNormalized running time'\n", "pp(elapsed_times_norm)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Simple Test Case Running Time relative to Pandas\n", "{\n", " \"blaze (pandas + bcolz)\": 0.36127805709838867, \n", " \"bquery + bcolz\": 0.31554603576660156, \n", " \"bquery + bcolz (fact. cached)\": 0.22467803955078125, \n", " \"bquery, create factorization cache\": 0.20379304885864258, \n", " \"pandas\": 0.10704803466796875\n", "}\n", "\n", "Normalized running time\n", "{\n", " \"blaze (pandas + bcolz)\": 3.3749153659753404, \n", " \"bquery + bcolz\": 2.9477050816050174, \n", " \"bquery + bcolz (fact. cached)\": 2.098852540802509, \n", " \"bquery, create factorization cache\": 1.9037532962725394, \n", " \"pandas\": 1.0\n", "}\n" ] } ], "prompt_number": 10 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Graphic Summary" ] }, { "cell_type": "code", "collapsed": false, "input": [ "if 'bquery, create factorization cache' in elapsed_times_norm:\n", " base_bquery = elapsed_times_norm.pop('bquery, create factorization cache')\n", "labels = []\n", "val = []\n", "for k,v in sorted(elapsed_times_norm.iteritems(), reverse=True):\n", " labels.append(k)\n", " val.append(v)\n", "\n", "pos = np.arange(len(elapsed_times_norm))+.5 # the bar centers on the y axis\n", "\n", "print elapsed_times_norm.keys()\n", "plt.figure(1, figsize=[15,5])\n", "plt.grid(True)\n", "plt.barh(pos,val, align='center')\n", "plt.barh(pos,[0, base_bquery, 0,0], \n", " left=[0, elapsed_times_norm['bquery + bcolz (fact. cached)'], 0, 0],\n", " align='center', color = '#FFFFCC')\n", "plt.yticks(pos, labels, fontsize=15)\n", "plt.xlabel('X times slower', fontsize=15)\n", "plt.title('Performance compared to pandas', fontsize=25)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "['pandas', 'bquery + bcolz (fact. cached)', 'blaze (pandas + bcolz)', 'bquery + bcolz']\n" ] }, { "metadata": {}, "output_type": "pyout", "prompt_number": 11, "text": [ "" ] }, { "metadata": {}, "output_type": "display_data", "png": "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\n+DGlTsmnW8ssJFeXAj+oMd2I0j3kW8BeE2prLDi+jVjnYsojc99GuXPmJ8ADM/PKY5CZB0XEeTXG\n/YE/UJ4uQlA/AAAgAElEQVQ2c2Gdf3RtzHkZ5Y6Zs4A3UhpCxvc3cgvg6pTj3dawvhFqb0r9l1nF\nYCVJkiRJG28wNTxWqoh4AqWh5Jajx65GxGnA4Zn5ol6D05KJiCOBH2TmSybMs4aHJEnSimYND2lT\nWMNj9fow5U6Ox7ambfSB1vIVEbsDdwEO6jsWSZIkSVotbPBY5rK4XWYe2p7cW0BacrV47Q6ZeUHf\nsWik6TuAAWr6DmCAmr4DGKCm7wAGqOk7gAFq+g5gcKwn0T1zvnIMqYbHqpGZN+07BkmSJEmSljNr\neEjLnDU8JEmSVjpreEibwhoekiRJkiRJlQ0ekjRL03cAA9T0HcAANX0HMEBN3wEMUNN3AAPU9B3A\n4FhPonvmfOWwwUOSJEmSJK061vCQljlreEiSJK101vCQNoU1PCRJkiRJkiobPCRplqbvAAao6TuA\nAWr6DmCAmr4DGKCm7wAGqOk7gMGxnkT3zPnKYYOHJEmSJEladazhIS1z1vCQJEla6azhIW0Ka3hI\nkiRJkiRVNnhI0ixN3wEMUNN3AAPU9B3AADV9BzBATd8BDFDTdwCDYz2J7pnzlcMGD0mSJEmStOpY\nw0Na5qzhIUmStNJZw0PaFNbwkCRJkiRJqmzwkKRZmr4DGKCm7wAGqOk7gAFq+g5ggJq+Axigpu8A\nBsd6Et0z5yuHDR6SJEmSJGnVsYaHtMxZw0OSJGmls4aHtCms4SFJkiRJklTZ4CFJszR9BzBATd8B\nDFDTdwAD1PQdwAA1fQcwQE3fAQyO9SS6Z85XjjV9ByBpITb67i1JkiRJGjRreEjLXESkn1NJkiRJ\nQ2UND0mSJEmSpMoGD0kaY7/M7pnz7pnz7pnz7pnz7pnz7pnz7pnzlcMGD0mSJEmStOpYw0Na5qzh\nIUmSJGnIrOEhSZIkSZJU2eAhSWPsl9k9c949c949c949c949c949c949c75y2OAhSZIkSZJWHWt4\nSMucNTwkSZIkDZk1PCRJkiRJkiobPCRpjP0yu2fOu2fOu2fOu2fOu2fOu2fOu2fOVw4bPCRJkiRJ\n0qpjDQ9pmbOGhyRJkqQhW2wNjzWbIxhJSytioz/bkiRJS84fYSStJHZpkVaEdOh0OHYZxDC0wZyb\n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\neAAAAABJRU5ErkJggg==\n", "text": [ "" ] } ], "prompt_number": 11 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The Light yellow shows the one-time factorization caching (which after the first run can be left out of future queries). \n", "NB: when data changes, this caching has to be refreshed but it is very useful in most reporting & analytics cases." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Complex scenario" ] }, { "cell_type": "code", "collapsed": false, "input": [ "elapsed_times = OrderedDict()\n", "\n", "ga = itt.cycle(['ES', 'NL'])\n", "gb = itt.cycle(['b1', 'b2', 'b3', 'b4'])\n", "gc = itt.cycle([1, 2])\n", "gd = itt.cycle([3, 4, 4, 3])\n", "ge = itt.cycle(['c','d','e'])\n", "gx = itt.cycle([1, 2])\n", "gy = itt.cycle([-1, -2])\n", "gz = itt.cycle([1.11, 2.22, 3.33, 4.44, 5.55])\n", "\n", "rootdir = 'bench-data.bcolz'\n", "if os.path.exists(rootdir):\n", " shutil.rmtree(rootdir)\n", "\n", "n_rows = 1000000\n", "print('Rows: ', n_rows)\n", " \n", "z = np.fromiter(((a, b, c, d, e, x, y, z) for a, b, c, d, e, x, y, z\n", " in itt.izip(ga, gb, gc, gd, ge, gx, gy, gz)),\n", " dtype='S2,S2,i4,i8,S1,i4,i8,f8', count=n_rows)\n", "ct = bquery.ctable(z, rootdir=rootdir, )\n", "\n", "# -- pandas --\n", "df = pd.DataFrame(z)\n", "with ctime(message='pandas'):\n", " result = df.groupby(['f0','f1','f2','f3','f4'], sort=False, as_index=False)['f5','f6','f7'].sum()\n", "# print(result)\n", "\n", "# -- bquery --\n", "with ctime(message='bquery + bcolz'):\n", " result = ct.groupby(['f0','f1','f2','f3','f4'], ['f5','f6','f7'])\n", "# print(result)\n", "\n", "with ctime(message='bquery, create factorization cache'):\n", " ct.cache_factor(['f0','f1','f2','f3','f4'], refresh=True)\n", " \n", "with ctime(message='bquery over bcolz (factorization cached)'):\n", " result = ct.groupby(['f0','f1','f2','f3','f4'], ['f5','f6','f7'])\n", "# print(result)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "('Rows: ', 1000000)\n", "\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "pandas: " ] }, { "output_type": "stream", "stream": "stdout", "text": [ " 0.4099 sec\n", "\n", "\n", "bquery + bcolz: " ] }, { "output_type": "stream", "stream": "stdout", "text": [ " 1.5813 sec\n", "\n", "\n", "bquery, create factorization cache: " ] }, { "output_type": "stream", "stream": "stdout", "text": [ " 0.4577 sec\n", "\n", "\n", "bquery over bcolz (factorization cached): " ] }, { "output_type": "stream", "stream": "stdout", "text": [ " 1.0691 sec\n" ] } ], "prompt_number": 12 }, { "cell_type": "code", "collapsed": false, "input": [ "print('Complex Test Case Running Time relative to Pandas')\n", "elapsed_times_bak = OrderedDict({ k: v for (k,v) in sorted(elapsed_times.iteritems())})\n", "pp(elapsed_times_bak)\n", "elapsed_times = elapsed_times_bak\n", "elapsed_times_norm = OrderedDict({ k: v/elapsed_times['pandas'] for (k,v) in sorted(elapsed_times.iteritems())})\n", "print '\\nNormalized running time'\n", "pp(elapsed_times_norm)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Complex Test Case Running Time relative to Pandas\n", "{\n", " \"bquery + bcolz\": 1.5812609195709229, \n", " \"bquery over bcolz (factorization cached)\": 1.0690908432006836, \n", " \"bquery, create factorization cache\": 0.45772814750671387, \n", " \"pandas\": 0.4098629951477051\n", "}\n", "\n", "Normalized running time\n", "{\n", " \"bquery + bcolz\": 3.8580231401497302, \n", " \"bquery over bcolz (factorization cached)\": 2.608410263569679, \n", " \"bquery, create factorization cache\": 1.1167832981402952, \n", " \"pandas\": 1.0\n", "}\n" ] } ], "prompt_number": 13 }, { "cell_type": "code", "collapsed": false, "input": [ "if 'bquery, create factorization cache' in elapsed_times_norm:\n", " base_bquery = elapsed_times_norm.pop('bquery, create factorization cache')\n", "labels = []\n", "val = []\n", "for k,v in sorted(elapsed_times_norm.iteritems(), reverse=True):\n", " labels.append(k)\n", " val.append(v)\n", "\n", "pos = np.arange(len(elapsed_times_norm))+.5 # the bar centers on the y axis\n", "\n", "print elapsed_times_norm.keys()\n", "plt.figure(1, figsize=[15,5])\n", "plt.grid(True)\n", "plt.barh(pos,val, align='center')\n", "plt.barh(pos,[0, base_bquery, 0], \n", " left=[0, elapsed_times_norm['bquery over bcolz (factorization cached)'], 0],\n", " align='center', color = '#FFFFCC')\n", "plt.yticks(pos, labels, fontsize=15)\n", "plt.xlabel('X times slower', fontsize=15)\n", "plt.title('Performance compared to pandas', fontsize=25)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "['pandas', 'bquery over bcolz (factorization cached)', 'bquery + bcolz']\n" ] }, { "metadata": {}, "output_type": "pyout", "prompt_number": 14, "text": [ "" ] }, { "metadata": {}, "output_type": "display_data", "png": "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\n", "text": [ "" ] } ], "prompt_number": 14 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Conclusion\n", "Currently -in our experience- the real-life performance of sum aggregations using on-disk bcolz queries depending on machine are normally between 2.0 and 3.0 times slower than similar in-memory Pandas aggregations.\n", "\n", "There are many scenarios where you would like to perform a number of groupby operations over a number of large files, creating issues with memory management and hot set selection. However, through bcolz and bquery you can have an out-of-core solution that is near in-memory in terms of size while creating the possibility to handle very large data sets with limited hardware.\n", "\n", "It is important to notice that while the end result is a bcolz ctable (which can be out-of-core) and the input can be any out-of-core ctable, the intermediate result will be an in-memory numpy array. This is because most groupby operations on non-sorted tables require random memory access while bcolz is limited to sequential access for optimum performance. However, this memory footprint is limited to the groupby result length and can be further optimized in the future to a per-column usage. \n" ] }, { "cell_type": "code", "collapsed": false, "input": [], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 14 } ], "metadata": {} } ] }