[Python-Dev] Investigating Python memory footprint of one real Web application

INADA Naoki songofacandy at gmail.com
Wed Jan 25 06:54:02 EST 2017


More detailed information:

## With annotations

=== tracemalloc stat ===
traced: (46969277, 46983753)
18,048,888 / 181112
  File "<frozen importlib._bootstrap_external>", line 488
  File "<frozen importlib._bootstrap_external>", line 780
  File "<frozen importlib._bootstrap_external>", line 675

=== size by types ===
dict                     9,083,816 (8,870.91KB) / 21846 = 415.811bytes (21.38%)
tuple                    6,420,960 (6,270.47KB) / 86781 = 73.990bytes (15.11%)
str                      6,050,213 (5,908.41KB) / 77527 = 78.040bytes (14.24%)
function                 2,772,224 (2,707.25KB) / 20384 = 136.000bytes (6.53%)
code                     2,744,888 (2,680.55KB) / 18987 = 144.567bytes (6.46%)
type                     2,713,552 (2,649.95KB) / 2769 = 979.975bytes (6.39%)
bytes                    2,650,838 (2,588.71KB) / 38723 = 68.456bytes (6.24%)
set                      2,445,280 (2,387.97KB) / 6969 = 350.880bytes (5.76%)
weakref                  1,255,600 (1,226.17KB) / 15695 = 80.000bytes (2.96%)
list                       707,336 (690.76KB) / 6628 = 106.719bytes (1.66%)

=== dict stat ===
t,         size,        total (%) / count
3,          256,    1,479,424 (15.68%) / 5779.0
3,        1,200,    1,330,800 (14.11%) / 1109.0
3,    1,310,832,    1,310,832 (13.90%) / 1.0
3,          664,    1,287,496 (13.65%) / 1939.0
7,          128,      756,352 (8.02%) / 5909.0
3,          384,      707,328 (7.50%) / 1842.0
3,        2,296,      642,880 (6.81%) / 280.0
0,          256,      378,112 (4.01%) / 1477.0
7,          168,      251,832 (2.67%) / 1499.0
3,        4,720,      221,840 (2.35%) / 47.0
3,        9,336,      130,704 (1.39%) / 14.0
7,           88,      105,072 (1.11%) / 1194.0

* t=7 key-sharing dict, t=3 interned string key only, t=1 string key
only, t=0 non string key is used


## Stripped annotations

=== tracemalloc stat ===
traced: (42383739, 42397983)

18,069,806 / 181346
  File "<frozen importlib._bootstrap_external>", line 488
  File "<frozen importlib._bootstrap_external>", line 780
  File "<frozen importlib._bootstrap_external>", line 675

=== size by types ===
dict                     7,913,144 (7,727.68KB) / 17598 = 449.662bytes (20.62%)
tuple                    6,149,120 (6,005.00KB) / 82734 = 74.324bytes (16.02%)
str                      6,070,083 (5,927.82KB) / 77741 = 78.081bytes (15.82%)
code                     2,744,312 (2,679.99KB) / 18983 = 144.567bytes (7.15%)
type                     2,713,552 (2,649.95KB) / 2769 = 979.975bytes (7.07%)
bytes                    2,650,464 (2,588.34KB) / 38715 = 68.461bytes (6.91%)
function                 2,547,280 (2,487.58KB) / 18730 = 136.000bytes (6.64%)
set                      1,423,520 (1,390.16KB) / 4627 = 307.655bytes (3.71%)
list                       634,472 (619.60KB) / 5454 = 116.331bytes (1.65%)
int                        608,784 (594.52KB) / 21021 = 28.961bytes (1.59%)

=== dict stat ===
t,         size,        total (%) / count
3,        1,200,    1,316,400 (16.06%) / 1097.0
3,    1,310,832,    1,310,832 (16.00%) / 1.0
3,          664,      942,216 (11.50%) / 1419.0
3,          256,      861,184 (10.51%) / 3364.0
3,          384,      657,024 (8.02%) / 1711.0
3,        2,296,      640,584 (7.82%) / 279.0
7,          128,      606,464 (7.40%) / 4738.0
0,          256,      379,904 (4.64%) / 1484.0
7,          168,      251,832 (3.07%) / 1499.0
3,        4,720,      221,840 (2.71%) / 47.0
3,        9,336,      130,704 (1.59%) / 14.0
7,           88,      105,248 (1.28%) / 1196.0
7,          256,       86,784 (1.06%) / 339.0


## Stripped annotation + without pydebug

=== tracemalloc stat ===
traced: (37371660, 40814265)
9,812,083 / 111082
  File "<frozen importlib._bootstrap>", line 205
  File "<frozen importlib._bootstrap_external>", line 742
  File "<frozen importlib._bootstrap_external>", line 782
6,761,207 / 85614
  File "<frozen importlib._bootstrap_external>", line 488
  File "<frozen importlib._bootstrap_external>", line 780
  File "<frozen importlib._bootstrap_external>", line 675


## Ideas about memory optimize

a) Split PyGC_Head from object

Reduces 2words (16byte) from each tuples.

>>> 82734 * 16 / 1024
1292.71875

So estimated -1.2MB


b) concat co_consts, co_names, co_varnames, co_freevars into one
tuple, or embed them into code.

Each tuple has 3 (gc head) + 3 (refcnt, *type, length) = 6 words
overhead. (or 4 words if (a) is applied)
If we can reduce 3 tuples, 18 words = 144byte (or 12 words=96byte) can
be reuduced.

>>> 18983 * 144
2733552
>>> 18983 * 96
1822368

But co_freevars is empty tuple in most cases.  So real effect is
smaller than 2.7MB.
If we can embed them into code object, we can estimate -2.7MB.

(There are co_cellvars too. But I don't know about it much, especially
it is GC tracked or not)


c) (interned) string key only dict.

20% of memory is used for dict, and 70% of dict has interned string keys.
Current DictKeyEntry is 3 words: {key, hash, value}.
But if we can assume all key is string, hash can be get from the key.

If we use 2 words entry: {key, value} for such dict, I think dict can
be 25% smaller.

>>> 7913144 * 0.25 / 1024
1931.919921875

So I estimate -1.9MB

If we can implement (a)~(c) I estimate memory usage on Python
(--without-pydebug)
can be reduced from 35.6MB to 30MB, roughly.

But I think -Onoannotation option is top priority.  It can reduce 4MB,
even we use
annotations only in our code.
If major libraries start using annotations, this number will be larger.


More information about the Python-Dev mailing list