P: n/a | Classes using __slots__ seem to be quite a bit smaller and faster to instantiate than regular Python classes using __dict__. Below are the results for the __slots__ and __dict__ version of a specific class with 16 attributes. Each line in the tables shows the number of instances created so far, the total memory usage in Bytes, the CPU time in secs, the average size per instance in Bytes and the average CPU time per instance in micseconds. Instances of this particular class with __slots__ are almost 6x smaller and nearly 3x faster to create than intances of the __dict__ version. Results for other classes will vary, obviously. Comments? /Jean Brouwers ProphICy Semiconductor, Inc. PS) The tests were run on a dual 2.4 GHz Xeon system with RedHat 8.0 and Python 2.3.2. The test script is attached but keep in mind that it only has been tested on Linux. It will not work elsewhere due to the implementation of the memory() function. testing __slots__ version ... 4096 insts so far: 3.0e+05 B 0.030 sec 73.0 B/i 7.3 usec/i 8192 insts so far: 8.8e+05 B 0.070 sec 107.5 B/i 8.5 usec/i 16384 insts so far: 1.5e+06 B 0.150 sec 92.2 B/i 9.2 usec/i 32768 insts so far: 3.3e+06 B 0.280 sec 101.0 B/i 8.5 usec/i 65536 insts so far: 6.6e+06 B 0.560 sec 101.2 B/i 8.5 usec/i 131072 insts so far: 1.4e+07 B 1.200 sec 103.4 B/i 9.2 usec/i 262144 insts so far: 2.7e+07 B 2.480 sec 103.4 B/i 9.5 usec/i 524288 insts so far: 5.5e+07 B 5.630 sec 104.0 B/i 10.7 usec/i 1048576 insts so far: 1.1e+08 B 13.980 sec 104.0 B/i 13.3 usec/i 1050000 insts total: 1.1e+08 B 14.000 sec 103.9 B/i 13.3 usec/i testing __dict__ version ... 4096 insts so far: 2.4e+06 B 0.050 sec 595.0 B/i 12.2 usec/i 8192 insts so far: 4.6e+06 B 0.090 sec 564.5 B/i 11.0 usec/i 16384 insts so far: 9.5e+06 B 0.180 sec 581.8 B/i 11.0 usec/i 32768 insts so far: 1.9e+07 B 0.370 sec 582.2 B/i 11.3 usec/i 65536 insts so far: 3.8e+07 B 0.830 sec 582.6 B/i 12.7 usec/i 131072 insts so far: 7.6e+07 B 1.760 sec 582.7 B/i 13.4 usec/i 262144 insts so far: 1.5e+08 B 4.510 sec 582.8 B/i 17.2 usec/i 524288 insts so far: 3.1e+08 B 12.820 sec 582.8 B/i 24.5 usec/i 1048576 insts so far: 6.1e+08 B 38.370 sec 583.1 B/i 36.6 usec/i 1050000 insts total: 6.1e+08 B 38.380 sec 583.1 B/i 36.6 usec/i -------------------------------slots.py------------------------------- <pre> from time import clock as time_clock def cputime(since=0.0): ''Return CPU in secs. '' return time_clock() - since import os _proc_status = '/proc/%d/status' % os.getpid() # Linux only _scale = {'kB': 1024.0, 'mB': 1024.0*1024.0, 'KB': 1024.0, 'MB': 1024.0*1024.0} def _VmB(VmKey): global _scale try: # get the /proc/<pid>/status pseudo file t = open(_proc_status) v = [v for v in t.readlines() if v.startswith(VmKey)] t.close() # convert Vm value to bytes if len(v) 1: t = v[0].split() # e.g. 'VmRSS: 9999 kB' if len(t) 3: ## and t[0] VmKey: return float(t[1]) * _scale.get(t[2], 0.0) except: pass return 0.0 def memory(since=0.0): ''Return process memory usage in bytes. '' return _VmB('VmSize:') - since def stacksize(since=0.0): ''Return process stack size in bytes. '' return _VmB('VmStk:') - since def slots(**kwds): ''Return the slots names as sequence. '' return tuple(kwds.keys()) # __slots__ version class SlotsClass(object): __slots__ = slots(_attr1= False, _attr2= None, _attr3= None, _attr4= None, _attr5= None, _attr6= None, _attr7= 0, _attr8= None, _attr9= None, _attr10=None, _attr11=None, _attr12=None, _attr13=None, _attr14=None, _attr15=None, _attr16=None) def __init__(self, tuple4, parent): self._attr1 = False self._attr2 = None self._attr3 = None self._attr4 = None self._attr5 = None self._attr6 = None if parent: self._attr7 = parent._attr7 + 1 self._attr8 = parent._attr8 self._attr9 = parent._attr9 self._attr10 = parent self._attr11 = parent._attr11 self._attr12 = parent._attr12 else: self._attr7 = 0 self._attr8 = None self._attr9 = None self._attr10 = None self._attr11 = self self._attr12 = None self._attr13, self._attr14, self._attr15, self._attr16 = tuple4 # __dict__ version class DictClass(object): _attr1 = None _attr2 = None _attr3 = None _attr4 = None _attr5 = None _attr6 = None _attr7 = 0 _attr8 = None _attr9 = None _attr10 = None _attr11 = None _attr12 = None _attr13 = None _attr14 = None _attr15 = None _attr16 = None def __init__(self, tuple4, parent): if parent: self._attr7 = parent._attr7 + 1 self._attr8 = parent._attr8 self._attr9 = parent._attr9 self._attr10 = parent self._attr11 = parent._attr11 self._attr12 = parent._attr12 else: self._attr11 = self self._attr13, self._attr14, self._attr15, self._attr16 = tuple4 if __name__ '__main__': import sys def report(txt, n, b0, c0): c = cputime(c0); b = memory(b0) print '%8d insts %s: %8.1e B %7.3f sec %6.1f B/i %6.1f usec/i' % (n, txt, b, c, b/n, 1.0e6*c/n) if not sys.platform.startswith('linux'): raise NotImplementedError, '%r not supported' % sys.platform if 'dict' in sys.argv[1:]: print 'testing __dict__ version ...' testClass = DictClass else: print 'testing __slots__ version ...' testClass = SlotsClass t4 = (', 0, 0, []) b0 = memory() c0 = cputime() p = testClass(t4, None) n, m = 1, 4096 # generate 1+ M instances while n < 1050000: # 1048576: p = testClass(t4, p) n += 1 if n >= m: # occasionally print stats m += m report('so far', n, b0, c0) report(' total', n, b0, c0) </pre>
|