Skip to main content

A memory profiler for data batch processing applications.

Reason this release was yanked:

Segfaults on Ubuntu

Project description

The Fil memory profiler for Python

Your code reads some data, processes it, and uses too much memory. In order to reduce memory usage, you need to figure out:

  1. Where peak memory usage is, also known as the high-water mark.
  2. What code was responsible for allocating the memory that was present at that peak moment.

That's exactly what Fil will help you find. Fil an open source memory profiler designed for data processing applications written in Python, and includes native support for Jupyter.

At the moment it only runs on Linux and macOS, and while it supports threading, it does not yet support multiprocessing or multiple processes in general.

"Within minutes of using your tool, I was able to identify a major memory bottleneck that I never would have thought existed. The ability to track memory allocated via the Python interface and also C allocation is awesome, especially for my NumPy / Pandas programs."

—Derrick Kondo

For more information, including an example of the output, see https://pythonspeed.com/products/filmemoryprofiler/

Fil vs. other Python memory tools

There are two distinct patterns of Python usage, each with its own source of memory problems.

In a long-running server, memory usage can grow indefinitely due to memory leaks. That is, some memory is not being freed.

  • If the issue is in Python code, tools like tracemalloc and Pympler can tell you which objects are leaking and what is preventing them from being leaked.
  • If you're leaking memory in C code, you can use tools like Valgrind.

Fil, however, is not aimed at memory leaks, but at the other use case: data processing applications. These applications load in data, process it somehow, and then finish running.

The problem with these applications is that they can, on purpose or by mistake, allocate huge amounts of memory. It might get freed soon after, but if you allocate 16GB RAM and only have 8GB in your computer, the lack of leaks doesn't help you.

Fil will therefore tell you, in an easy to understand way:

  1. Where peak memory usage is, also known as the high-water mark.
  2. What code was responsible for allocating the memory that was present at that peak moment.
  3. This includes C/Fortran/C++/whatever extensions that don't use Python's memory allocation API (tracemalloc only does Python memory APIs).

This allows you to optimize that code in a variety of ways.

Installation

Assuming you're on macOS or Linux, and are using Python 3.6 or later, you can use either Conda or pip (or any tool that is pip-compatible and can install manylinux2010 wheels).

Conda

To install on Conda:

$ conda install -c conda-forge filprofiler

Pip

To install the latest version of Fil you'll need Pip 19 or newer. You can check like this:

$ pip --version
pip 19.3.0

If you're using something older than v19, you can upgrade by doing:

$ pip install --upgrade pip

If that doesn't work, try running your code in a virtualenv:

$ python3 -m venv venv/
$ . venv/bin/activate
(venv) $ pip install --upgrade pip

Assuming you have a new enough version of pip:

$ pip install filprofiler

Using Fil

Profiling in Jupyter

To measure peak memory usage of some code in Jupyter you need to do three things:

  1. Use an alternative kernel, "Python 3 with Fil". You can choose this kernel when you create a new notebook, or you can switch an existing notebook in the Kernel menu; there should be a "Change Kernel" option in there in both Jupyter Notebook and JupyterLab.
  2. Load the extension by doing %load_ext filprofiler.
  3. Add the %%filprofile magic to the top of the cell with the code you wish to profile.

Screenshot of JupyterLab

Profiling complete Python programs

Instead of doing:

$ python yourscript.py --input-file=yourfile

Just do:

$ fil-profile run yourscript.py --input-file=yourfile

And it will generate a report and automatically try to open it in for you in a browser. Reports will be stored in the fil-result/ directory in your current working directory.

If your program is usually run as python -m yourapp.yourmodule --args, you can do that with Fil too:

$ fil-profile run -m yourapp.yourmodule --args

As of version 0.11, you can use python -m to run Fil:

$ python -m filprofiler run yourscript.py --input-file=yourfile

As of version 2021.04.2, you can disable opening reports in a browser by using the --no-browser option (see fil-profile --help for details). You will want to view the SVG report in a browser, since they rely heavily on JavaScript. If you want to serve the report files from a static directory from a web server, you can use python -m http.server.

API for profiling specific Python functions

You can also measure memory usage in part of your program; this requires version 0.15 or later. This requires two steps.

1. Add profiling in your code

Let's you have some code that does the following:

def main():
    config = load_config()
    result = run_processing(config)
    generate_report(result)

You only want to get memory profiling for the run_processing() call.

You can do so in the code like so:

from filprofiler.api import profile

def main():
    config = load_config()
    result = profile(lambda: run_processing(config), "/tmp/fil-result")
    generate_report(result)

You could also make it conditional, e.g. based on an environment variable:

import os
from filprofiler.api import profile

def main():
    config = load_config()
    if os.environ.get("FIL_PROFILE"):
        result = profile(lambda: run_processing(config), "/tmp/fil-result")
    else:
        result = run_processing(config)
    generate_report(result)

2. Run your script with Fil

You still need to run your program in a special way. If previously you did:

$ python yourscript.py --config=myconfig

Now you would do:

$ filprofiler python yourscript.py --config=myconfig

Notice that you're doing filprofiler python, rather than filprofiler run as you would if you were profiling the full script. Only functions explicitly called with the filprofiler.api.profile() will have memory profiling enabled; the rest of the code will run at (close) to normal speed and configuration. Each call to profile() will generate a separate report.

The memory profiling report will be written to the directory specified as the output destination when calling profile(); in or example above that was "/tmp/fil-result". Unlike full-program profiling:

  1. The directory you give will be used directly, there won't be timestamped sub-directories. If there are multiple calls to profile(), it is your responsibility to ensure each call writes to a unique directory.
  2. The report(s) will not be opened in a browser automatically, on the presumption you're running this in an automated fashion.

Debugging out-of-memory crashes

New in v0.14 and later: Just run your program under Fil, and it will generate a SVG at the point in time when memory runs out, and then exit with exit code 53:

$ fil-profile run oom.py 
...
=fil-profile= Wrote memory usage flamegraph to fil-result/2020-06-15T12:37:13.033/out-of-memory.svg

Fil uses three heuristics to determine if the process is close to running out of memory:

  • A failed allocation, indicating insufficient memory is available.
  • The operating system or memory-limited cgroup (e.g. a Docker container) only has 100MB of RAM available.
  • The process swap is larger than available memory, indicating heavy swapping by the process. In general you want to avoid swapping, and e.g. explicitly use mmap() if you expect to be using disk as a backfill for memory.

Disabling the out-of-memory detection

Sometimes the out-of-memory detection heuristic will kick in too soon, shutting down the program even though in practice it could finish running. You can disable the heuristic by doing fil-profile --disable-oom-detection run yourprogram.py.

Reducing memory usage in your code

You've found where memory usage is coming from—now what?

If you're using data processing or scientific computing libraries, I have written a relevant guide to reducing memory usage.

How Fil works

Fil uses the LD_PRELOAD/DYLD_INSERT_LIBRARIES mechanism to preload a shared library at process startup. This shared library captures all memory allocations and deallocations and keeps track of them.

At the same time, the Python tracing infrastructure (used e.g. by cProfile and coverage.py) to figure out which Python callstack/backtrace is responsible for each allocation.

For performance reasons, only the largest allocations are reported, with a minimum of 99% of allocated memory reported. The remaining <1% is highly unlikely to be relevant when trying to reduce usage; it's effectively noise.

Fil and threading, with notes on NumPy and Zarr {#threading}

In general, Fil will track allocations in threads correctly.

First, if you start a thread via Python, running Python code, that thread will get its own callstack for tracking who is responsible for a memory allocation.

Second, if you start a C thread, the calling Python code is considered responsible for any memory allocations in that thread. This works fine... except for thread pools. If you start a pool of threads that are not Python threads, the Python code that created those threads will be responsible for all allocations created during the thread pool's lifetime.

Therefore, in order to ensure correct memory tracking, Fil disables thread pools in BLAS (used by NumPy), BLOSC (used e.g. by Zarr), OpenMP, and numexpr. They are all set to use 1 thread, so calls should run in the calling Python thread and everything should be tracked correctly.

This has some costs:

  1. This can reduce performance in some cases, since you're doing computation with one CPU instead of many.
  2. Insofar as these libraries allocate memory proportional to number of threads, the measured memory usage might be wrong.

Fil does this for the whole program when using fil-profile run. When using the Jupyter kernel, anything run with the %%filprofile magic will have thread pools disabled, but other code should run normally.

What Fil tracks

Fil will track memory allocated by:

  • Normal Python code.
  • C code using malloc()/calloc()/realloc()/posix_memalign().
  • C++ code using new (including via aligned_alloc()).
  • Anonymous mmap()s.
  • Fortran 90 explicitly allocated memory (tested with gcc's gfortran).

Still not supported, but planned:

  • mremap() (resizing of mmap()).
  • File-backed mmap(). The semantics are somewhat different than normal allocations or anonymous mmap(), since the OS can swap it in or out from disk transparently, so supporting this will involve a different kind of resource usage and reporting.
  • Other forms of shared memory, need to investigate if any of them allow sufficient allocation.
  • Anonymous mmap()s created via /dev/zero (not common, since it's not cross-platform, e.g. macOS doesn't support this).
  • memfd_create(), a Linux-only mechanism for creating in-memory files.
  • Possibly memalign, valloc(), pvalloc(), reallocarray(). These are all rarely used, as far as I can tell.

License

Copyright 2020 Hyphenated Enterprises LLC

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

filprofiler-2021.7.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (10.4 MB view details)

Uploaded CPython 3.9manylinux: glibc 2.12+ x86-64

filprofiler-2021.7.0-cp39-cp39-macosx_10_15_x86_64.whl (461.8 kB view details)

Uploaded CPython 3.9macOS 10.15+ x86-64

filprofiler-2021.7.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (7.8 MB view details)

Uploaded CPython 3.8manylinux: glibc 2.12+ x86-64

filprofiler-2021.7.0-cp38-cp38-macosx_10_15_x86_64.whl (461.5 kB view details)

Uploaded CPython 3.8macOS 10.15+ x86-64

filprofiler-2021.7.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (5.2 MB view details)

Uploaded CPython 3.7mmanylinux: glibc 2.12+ x86-64

filprofiler-2021.7.0-cp37-cp37m-macosx_10_15_x86_64.whl (461.8 kB view details)

Uploaded CPython 3.7mmacOS 10.15+ x86-64

filprofiler-2021.7.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (2.6 MB view details)

Uploaded CPython 3.6mmanylinux: glibc 2.12+ x86-64

filprofiler-2021.7.0-cp36-cp36m-macosx_10_15_x86_64.whl (461.9 kB view details)

Uploaded CPython 3.6mmacOS 10.15+ x86-64

File details

Details for the file filprofiler-2021.7.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl.

File metadata

File hashes

Hashes for filprofiler-2021.7.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl
Algorithm Hash digest
SHA256 c6613b5136d3a65fd06b061b5f24ac90ae626e150ed3a07e11e692e55b0fd7c0
MD5 8aa52ff08babea0aa6368f78807f297b
BLAKE2b-256 7825129b051162a13f1070a73ff5c68afe21a120a78b34519579224d72ed7ebd

See more details on using hashes here.

File details

Details for the file filprofiler-2021.7.0-cp39-cp39-macosx_10_15_x86_64.whl.

File metadata

  • Download URL: filprofiler-2021.7.0-cp39-cp39-macosx_10_15_x86_64.whl
  • Upload date:
  • Size: 461.8 kB
  • Tags: CPython 3.9, macOS 10.15+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/4.6.1 pkginfo/1.7.1 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.61.2 CPython/3.9.5

File hashes

Hashes for filprofiler-2021.7.0-cp39-cp39-macosx_10_15_x86_64.whl
Algorithm Hash digest
SHA256 9d6c442240ee240a813a5f5cff788d6ed3c80c86d7a56f7d7d98217e7f6fa1ec
MD5 b31c587478f0d4fb7eaf722caae8ea4f
BLAKE2b-256 8716c29a956c19f16288fed1183db3e805f0b514693a6454e9765dc9cc691c74

See more details on using hashes here.

File details

Details for the file filprofiler-2021.7.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl.

File metadata

File hashes

Hashes for filprofiler-2021.7.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl
Algorithm Hash digest
SHA256 7bc509d8b515c3f9356acd201d1be76547045b499337db248edb92aa37f79dd0
MD5 21c8d354ee22d48f3fd84ead129e5449
BLAKE2b-256 5c602c6e49badf41618d5a0688410db0b797d1f21bbe68e121949efd42389049

See more details on using hashes here.

File details

Details for the file filprofiler-2021.7.0-cp38-cp38-macosx_10_15_x86_64.whl.

File metadata

  • Download URL: filprofiler-2021.7.0-cp38-cp38-macosx_10_15_x86_64.whl
  • Upload date:
  • Size: 461.5 kB
  • Tags: CPython 3.8, macOS 10.15+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/4.6.1 pkginfo/1.7.1 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.61.2 CPython/3.8.10

File hashes

Hashes for filprofiler-2021.7.0-cp38-cp38-macosx_10_15_x86_64.whl
Algorithm Hash digest
SHA256 08e5c04540e707bf838cb2fa69b677be76dc79e5c6ea340ddbb278a723c27fbb
MD5 5a5fbb0079acfb29083e5b108bae6b1c
BLAKE2b-256 6d04c47b2daad5423cbb64c40a14a1e28fd5b594f75091263b3c1b813483851d

See more details on using hashes here.

File details

Details for the file filprofiler-2021.7.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl.

File metadata

File hashes

Hashes for filprofiler-2021.7.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl
Algorithm Hash digest
SHA256 6c5720957368e7442b2507a7d6815c84fc27cd37bc59eb29158eba1b22e73720
MD5 5d5f88c56d0dfb6a9838c709b04d9995
BLAKE2b-256 18dd2959abd0bab3c83ae7dc4a84358a3fbb9fb32abe8b8e96655a977af81a9e

See more details on using hashes here.

File details

Details for the file filprofiler-2021.7.0-cp37-cp37m-macosx_10_15_x86_64.whl.

File metadata

  • Download URL: filprofiler-2021.7.0-cp37-cp37m-macosx_10_15_x86_64.whl
  • Upload date:
  • Size: 461.8 kB
  • Tags: CPython 3.7m, macOS 10.15+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/3.10.1 pkginfo/1.7.1 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.61.2 CPython/3.7.10

File hashes

Hashes for filprofiler-2021.7.0-cp37-cp37m-macosx_10_15_x86_64.whl
Algorithm Hash digest
SHA256 69d06eb525de32a2a1f501b247d9bd75233b2c5b37120798276a8c04026c290d
MD5 7203c719142b6200b2518b51ee2c24c3
BLAKE2b-256 b39f3a52704500f2e56bcbe7e4d9bb0bab3850fd8e5155c6d8506ddc7aa1f1a9

See more details on using hashes here.

File details

Details for the file filprofiler-2021.7.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl.

File metadata

File hashes

Hashes for filprofiler-2021.7.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl
Algorithm Hash digest
SHA256 2b2a624d1f4173652a0bb4d4e1fac852e4d3179ec399ea3cfc20928f18c4ddef
MD5 4208410e6f10244d174b8af654ceac53
BLAKE2b-256 35188032404449cba29c9e682b99a542f0826d2b67c31bd59af28f7ca1ce9fce

See more details on using hashes here.

File details

Details for the file filprofiler-2021.7.0-cp36-cp36m-macosx_10_15_x86_64.whl.

File metadata

  • Download URL: filprofiler-2021.7.0-cp36-cp36m-macosx_10_15_x86_64.whl
  • Upload date:
  • Size: 461.9 kB
  • Tags: CPython 3.6m, macOS 10.15+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/4.6.1 pkginfo/1.7.1 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.61.2 CPython/3.6.13

File hashes

Hashes for filprofiler-2021.7.0-cp36-cp36m-macosx_10_15_x86_64.whl
Algorithm Hash digest
SHA256 8643979537ae0441b1d2f75a68da9aad50ebec7a08b311dbc2eca45248d490dc
MD5 b1a3ced13107d353f5e3a949dc0941c6
BLAKE2b-256 eee6dbc4b799204c44a1c41093ae97e0937f750928703e0628e66a4bc6394b95

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page