Skip to content

ENH added optional tracemalloc backend#117

Merged
fabianp merged 12 commits intopythonprofilers:masterfrom
demiurg906:tracemalloc-backend
Aug 19, 2016
Merged

ENH added optional tracemalloc backend#117
fabianp merged 12 commits intopythonprofilers:masterfrom
demiurg906:tracemalloc-backend

Conversation

@demiurg906
Copy link
Copy Markdown
Contributor

It is now possible to use tracemalloc to analyze memory usage Python
code on Python 3.4 and above. tracemalloc allows for more precise
measurements compared to psutil. However, it only works either for
pure Python code or for C extensions allocating memory via PyMem_Alloc.

To use the new backend code run memory_profiler with --backend
option, e.g.

$ python -m memory_profiler --backend=tracemalloc script.py

Also if you use memory_profiler with imported decorator you can specify
backend as an argument to the decorator function:

@profile(backend='tracemalloc')
def f(n):
    a = [0] * n
    return a

backend parameter to @profile has priority over --backend.

Note that using tracemalloc in mprof and IPython magic is not
supported at the moment.

It is now possible to use ``tracemalloc`` to analyze memory usage Python
code on Python 3.4 and above. ``tracemalloc`` allows for more precise
measurements compared to ``psutil``. However, it only works either for
pure Python code or for C extensions allocating memory via ``PyMem_Alloc``.

To use the new backend code run ``memory_profiler`` with ``--backend``
option, e.g.

    $ python -m memory_profiler --backend=tracemalloc script.py

Also if you use ``memory_profiler`` with imported decorator you can specify
backend as an argument to the decorator function:

    @Profile(backend='tracemalloc')
    def f(n):
        a = [0] * n
        return a

``backend`` parameter to ``@profile`` has priority over ``--backend``.

Note that using ``tracemalloc`` in ``mprof`` and IPython magic is not
supported at the moment.
Comment thread memory_profiler.py

_backend_chosen = False
_backend = 'psutil'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need two global variables? I think you can do with only one (possibly initialized to None).

@fabianp
Copy link
Copy Markdown
Collaborator

fabianp commented Jul 13, 2016

Thanks for the contribution!!

Looks great. If I understood it correctly, the default backends by precedence are psutil -> posix (if on posix) -> tracemalloc, right? (this is perfect, just want to be sure I understood).

Also, what do you mean by all those "# TODO: add filename" ? could you remove them if they are not important?

Comment thread memory_profiler.py Outdated
_backend = n_backend
break
if _backend == 'no_backend':
raise NotImplementedError('Tracemalloc or psutil module is required for non-unix '
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A RuntimeError would be more appropriate here I think.

@demiurg906
Copy link
Copy Markdown
Contributor Author

Looks great. If I understood it correctly, the default backends by precedence are psutil -> posix (if on posix) -> tracemalloc, right? (this is perfect, just want to be sure I understood).

@fabianp, yes you understood correctly

Also, what do you mean by all those "# TODO: add filename" ? could you remove them if they are not important?

That TODO i added in places where necessary add some code to support tracemalloc, but i not invented now how realize that

@fabianp
Copy link
Copy Markdown
Collaborator

fabianp commented Jul 15, 2016

I don't understand your last statement. Do you mean that the support for tracemalloc is not complete?

@demiurg906
Copy link
Copy Markdown
Contributor Author

Yes, you right. tracemalloc now works only in profiling with @profile decorator with LineProfiler or TimeStamper.

Note that using tracemalloc in mprof and IPython magic is not supported at the moment.

@fabianp
Copy link
Copy Markdown
Collaborator

fabianp commented Jul 19, 2016

@demiurg906 let me know when you think this is ready to be merged

@superbobry
Copy link
Copy Markdown

superbobry commented Aug 4, 2016

Hi @fabianp, I wanted to quickly summarize the status of this PR.

The good part: tracemalloc produces more accurate results for pure Python code (see tests).

The not-so-good parts:

  • C allocations (including NumPy) aren't measured, which is a fundamental problem with tracemalloc at the moment.
  • It is much slower than psutil, because tracemalloc accumulates significantly more information than needed for memory_profiler.

The last issue can be partially "solved" by restarting tracing after each line of code. This brings tracemalloc up to speed with psutil, but makes it miss all deallocations, since it no longer tracks memory between lines.

Another way to approach the speed issue is to write a stripped down version of tracemalloc. Python3.4 introduced custom allocator API which is how tracemalloc does its bookkeeping. It shouldn't be very hard to do the same either in C or Cython.

I think deallocations are a must-have and therefore the only real option we have is to write a custom allocator. What do you think?

@fabianp
Copy link
Copy Markdown
Collaborator

fabianp commented Aug 9, 2016

thanks for the update @superbobry . For now I would like to avoid including any compiled code. For me, the benefit in speed would not compensate the deployment and maintenance burden.

I think the current code is good enough, and we can always improve upon. Is there something left to do?

Comment thread memory_profiler.py Outdated
self.include_children = kw.pop("include_children", False)

# get baseline memory usage
# TODO: add filename
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@demiurg906 is this (and the following) TODO still relevant?

@demiurg906
Copy link
Copy Markdown
Contributor Author

@fabianp, I fix all remaining comments, so I think PR is ready to merge

Comment thread memory_profiler.py
return OrderedDict(items)
if new_backend is not None:
backends = move_to_start(backends, new_backend)
backends.insert(0, backends.pop(backends_indices[new_backend]))
Copy link
Copy Markdown

@superbobry superbobry Aug 11, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After this line backends_indices will become invalid, right?

Update: looks like backends_indices isn't used anywhere in the code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, backends_indices is used only in moving chosen backend

@fabianp
Copy link
Copy Markdown
Collaborator

fabianp commented Aug 13, 2016

Great work @demiurg906! .

I've added a commit on top of yours that removes the global variable _backend. Now that variable is passed along the classes to _get_memory, an approach that I prefer. The code is in PR #119 . Could you please take a look at the resulting code / try it on your problems? If it looks good to you, then we are all set!

@fabianp fabianp merged commit 946aa5e into pythonprofilers:master Aug 19, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants