Skip to content

Instantly share code, notes, and snippets.

@davisagli
Last active August 8, 2023 16:10
Show Gist options
  • Save davisagli/7407505 to your computer and use it in GitHub Desktop.
Save davisagli/7407505 to your computer and use it in GitHub Desktop.
Experiments with Plone object RAM use (ipython notebook viewer: http://nbviewer.ipython.org/7407505)
Display the source blob
Display the rendered blob
Raw
{
"metadata": {
"name": "RAM experiments"
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": "I've got an app with about a hundred thousand Dexterity items. It takes quite a bit of memory to iterate over them. Let's investigate that.\n\nWe can measure the increase in Python's RAM usage while we load 10000 Contacts from the database (from the \"members\" folder's BTree of contained items):"
},
{
"cell_type": "code",
"collapsed": false,
"input": "COUNT = 10000\nSTEP = 100\nx = np.linspace(1, COUNT, COUNT / STEP)\ny = np.empty(COUNT / STEP, dtype=int)\n\ni = 0\nfor contact in site.members._tree.itervalues():\n contact.getId() # make sure the object's not a ZODB ghost\n if not i % STEP:\n y[i / STEP] = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1000\n i += 1\n if i >= COUNT:\n break\n\nplt.plot(x, y, 'bo')\nplt.xlabel('Objects loaded')\nplt.ylabel('RAM used (KB)')",
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "pyout",
"prompt_number": 2,
"text": "<matplotlib.text.Text at 0x118b2d150>"
},
{
"output_type": "display_data",
"png": "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\nTkSuQmCC\n"
}
],
"prompt_number": 2
},
{
"cell_type": "markdown",
"metadata": {},
"source": "That looks like pretty linear growth. Let's make an estimate of the RAM used per Contact."
},
{
"cell_type": "code",
"collapsed": false,
"input": "contact_size = (y[-1] - y[0]) / float(COUNT)\nprint '%0.2f KB' % contact_size",
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": "7.99 KB\n"
}
],
"prompt_number": 3
},
{
"cell_type": "markdown",
"metadata": {},
"source": "The ZODB cache keeps track of an estimated size for each object. It actually tracks the size of the object's pickle, not the size of the object in RAM. For example, here's the pickle size of the contact representing me:"
},
{
"cell_type": "code",
"collapsed": false,
"input": "contact = site.members['david-glick']\ncontact.getId()\ncontact_pickle_size = contact._p_estimated_size / 1000.0\nprint '%0.2f KB' % contact_pickle_size",
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": "2.75 KB\n"
}
],
"prompt_number": 7
},
{
"cell_type": "markdown",
"metadata": {},
"source": "Taking the ratio of these we can estimate a compression ratio of space used by Contacts in RAM compared to pickled Contacts stored in the ZODB:\n\n(XXX should take average of many contacts)"
},
{
"cell_type": "code",
"collapsed": false,
"input": "print '%0.2f' % (contact_size / contact_pickle_size)",
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": "2.90\n"
}
],
"prompt_number": 8
},
{
"cell_type": "markdown",
"metadata": {},
"source": "Okay, moving along...so what's actually taking up those 8 KB? We can analyze the size of the object, including the contents of its instance dictionary."
},
{
"cell_type": "code",
"collapsed": false,
"input": "from IPython.display import HTML\ndef analyze_dict(o):\n h = '<table>'\n h += '<tr><th>Key</th><th>Value</th><th>Size (Bytes)</th></tr>'\n total = 0\n\n size = sys.getsizeof(o)\n h += '<tr><td colspan=\"2\">[obj]</td><td>%s</td></tr>' % size\n total += size\n\n d = vars(o)\n size = sys.getsizeof(d)\n h += '<tr><td colspan=\"2\">[obj.__dict__]</td><td>%s</td></tr>' % size\n total += size\n\n for k in sorted(d):\n v = d[k]\n if hasattr(v, '_p_jar'):\n # separate persistent item; don't count its size\n size = sys.getsizeof(k)\n else:\n size = sys.getsizeof(k) + sys.getsizeof(v)\n total += size\n h += '<tr><td>%s</td><td>%s</td><td>%s</td>' % (k, repr(v), size)\n h += '<tr><th colspan=\"2\">(TOTAL)</th><th>%s</th>' % total\n h += '</table>'\n return HTML(h)\nanalyze_dict(contact)",
"language": "python",
"metadata": {},
"outputs": [
{
"html": "<table><tr><th>Key</th><th>Value</th><th>Size (Bytes)</th></tr><tr><td colspan=\"2\">[obj]</td><td>80</td></tr><tr><td colspan=\"2\">[obj.__dict__]</td><td>3352</td></tr><tr><td>__ac_local_roles__</td><td>{'admin': ['Owner']}</td><td>335</td><tr><td>__annotations__</td><td><BTrees.OOBTree.OOBTree object at 0x118b215d0></td><td>--</td><tr><td>_count</td><td><BTrees.Length.Length object at 0x118b52398></td><td>--</td><tr><td>_foo</td><td>1</td><td>65</td><tr><td>_mt_index</td><td><BTrees.OOBTree.OOBTree object at 0x118b21650></td><td>--</td><tr><td>_plone.uuid</td><td>'f64e8739b51e4633bec28439ed996f24'</td><td>117</td><tr><td>_tree</td><td><BTrees.OOBTree.OOBTree object at 0x113cebed0></td><td>--</td><tr><td>autorenew_opt_in</td><td>True</td><td>77</td><tr><td>bio</td><td>u'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\\r\\ntempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\\r\\nquis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\\r\\nconsequat. Duis aute irure dolor in reprehenderit in voluptate velit esse\\r\\ncillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non\\r\\nproident, sunt in culpa qui officia deserunt mollit anim id est laborum.'</td><td>992</td><tr><td>birthdate</td><td>datetime.date(1953, 6, 19)</td><td>78</td><tr><td>branch</td><td>u'Seattle'</td><td>107</td><tr><td>city</td><td>u'Seattle'</td><td>105</td><tr><td>contributors</td><td>()</td><td>105</td><tr><td>country</td><td>u'USA'</td><td>100</td><tr><td>creation_date</td><td>DateTime('2013/07/02 17:21:31.932419 GMT-7')</td><td>202</td><tr><td>creators</td><td>('admin', 'f64e8739b51e4633bec28439ed996f24')</td><td>117</td><tr><td>customer_id</td><td>u'cus_2EEjL7sIZ1u9fu'</td><td>134</td><tr><td>description</td><td>u''</td><td>98</td><tr><td>effective_date</td><td>None</td><td>67</td><tr><td>email</td><td>u'dglick@gmail.com'</td><td>124</td><tr><td>email_opt_in</td><td>True</td><td>73</td><tr><td>emergency_contact_name</td><td>u'asdf'</td><td>117</td><tr><td>emergency_contact_phone</td><td>u'1234'</td><td>118</td><tr><td>employer</td><td>u'Glick Software'</td><td>123</td><tr><td>ethnicity</td><td>None</td><td>62</td><tr><td>expiration_date</td><td>None</td><td>68</td><tr><td>first_name</td><td>u'D\\xe4vid'</td><td>107</td><tr><td>format</td><td>'text/html'</td><td>89</td><tr><td>full_bio</td><td>RichTextValue object. (Did you mean <attribute>.raw or <attribute>.output?)</td><td>109</td><tr><td>gender</td><td>u'Male'</td><td>101</td><tr><td>home_phone</td><td>None</td><td>63</td><tr><td>id</td><td>'david-glick'</td><td>87</td><tr><td>imis_id</td><td>None</td><td>60</td><tr><td>interested_in_volunteering</td><td>False</td><td>87</td><tr><td>interests</td><td>set([u'Backpacking', u'First Aid'])</td><td>278</td><tr><td>is_private</td><td>False</td><td>71</td><tr><td>language</td><td>''</td><td>82</td><tr><td>last_name</td><td>u'Glick'</td><td>106</td><tr><td>legal_name</td><td>u'David Glick'</td><td>119</td><tr><td>m_contacts</td><td>set(['e4636db205f24fd588ef6ca076b4e9cc', '5b6a89edc6e54906972e30c78979be92', '12760db7f8214e8783beca8e02a64b90'])</td><td>279</td><tr><td>m_expires</td><td>datetime.date(2014, 8, 31)</td><td>78</td><tr><td>m_start</td><td>datetime.datetime(2013, 8, 14, 13, 19)</td><td>92</td><tr><td>m_type</td><td>'fed3467af524457fbeff2f628d0eb999'</td><td>112</td><tr><td>m_uid</td><td>'0b27776bf4ca462b8849126bd432c280'</td><td>111</td><tr><td>magazine_opt_in</td><td>True</td><td>76</td><tr><td>mobile_opt_in</td><td>True</td><td>74</td><tr><td>mobile_phone</td><td>None</td><td>65</td><tr><td>modification_date</td><td>DateTime('2013/11/06 17:13:55.300198 US/Eastern')</td><td>206</td><tr><td>occupation</td><td>u'Programmer'</td><td>117</td><tr><td>password</td><td>u'{SSHA}************************************'</td><td>179</td><tr><td>portal_type</td><td>'mtneers.contact'</td><td>100</td><tr><td>portrait</td><td><plone.namedfile.file.NamedBlobImage object at 0x118b52140></td><td>--</td><tr><td>postal_code</td><td>u'98107'</td><td>108</td><tr><td>preferred_phone</td><td>u'Home'</td><td>110</td><tr><td>rights</td><td>''</td><td>80</td><tr><td>sf_data_digest</td><td>\"+\\xb73\\x19\\x00\\x14\\x86\\x01Zm\\xcee%\\xd1Ng\\xe5'\\xd5\\x9a\"</td><td>108</td><tr><td>state</td><td>u'WA'</td><td>96</td><tr><td>street</td><td>u'1541 NW 52nd St.'</td><td>125</td><tr><td>subject</td><td>()</td><td>100</td><tr><td>twitter</td><td>u'davisagli'</td><td>112</td><tr><td>username</td><td>'david'</td><td>87</td><tr><th colspan=\"2\">(TOTAL)</th><th>10590</th></table>",
"output_type": "pyout",
"prompt_number": 10,
"text": "<IPython.core.display.HTML at 0x118b2d090>"
}
],
"prompt_number": 10
},
{
"cell_type": "markdown",
"metadata": {},
"source": "A bit larger than our estimate, which is probably because I've been using this contact for testing and it has more fields filled in than most.\n\nNotice that the Contact object stores many app-specific fields, in addition to some \"internal\" Zope/Plone attributes:\n\n* \\__ac_local_roles\\__: Local role assignments\n* \\__annotations\\__: A BTree of arbitrary annotations\n* _count: Count of contained items\n* _mt_index: Mapping of meta_type -> id of contained item\n* _plone.uuid: A UUID\n* _tree: BTree with contained items\n* portal_type: Identifies the content type of this item\n\nThe sizes given for a particular attribute are the sum of the sizes of the key and value. Some of the fields are separate persistent objects, and in those cases I'm not counting the size of the value.\n\nBased on this, we can identify a number of strategies for reducing RAM use:\n\n* The largest single item in this table is the instance dictionary itself; that is, the dictionary's internal hash table structure. Keep in mind that this dictionary is duplicated for every Contact even though each one has the same keys. (Side note: in theory this problem would mostly go away if Plone ran on Python 3.3, which can share the keys and hash tables of instance dictionaries).\n\n A big savings should come from making the Contact class define \\__slots\\__, so that the object's data is stored as a tuple rather than as a dictionary. This will avoid the existence of the instance dict altogether AND avoid the per-instance overhead of the strings representing the attribute names (XXX unless these are already intern'd???). This will take some work, though, because we need to make sure that the object can still get pickled and unpickled properly. Also, we can't use code from Dexterity base classes that assumes the item has a \\__dict\\__.\n\n* The bio could become a RichText field (even if we keep the mimetype as text/plain) so that the value is stored as a separate persistent object.\n\n* Fields which store dicts or sets are also up there: \\__ac_local_roles\\__, interests, m_contacts. The sets could be replaced with tuples for storage, still using an unordered widget. \\__ac_local_roles\\__ could be replaced with a field that stores a single owner, since that is the most common local role assignment (though this would be a Zope-level change, so tricky).\n\n* We could store the int representation of UUIDs rather than the string representation. It's a small savings and would require changing a lot of app code though.\n\n* portal_type doesn't need to be stored on the instance, because it can be inherited from the Contact class via Python mro.\n"
}
],
"metadata": {}
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment