Skip to content

Performance Profiling

This page covers a few tools for finding out where the time goes when you use pymmcore-plus. Every device behaves differently, so slowdowns depend a lot on your hardware and on what you're doing with it: measure on your own setup.

If you just want a quick look at how long common core and device calls take with your hardware, mmcore bench runs a short benchmark (with the demo configuration, or your own with --config my.cfg) and prints a table of timings.

Profiling with py-spy

There are many tools available for profiling Python code. We recommend starting with py-spy. You can add it to your environment with pip install py-spy. For complete usage tips, see the py-spy readme, but here is a quick example of how to use it with pymmcore-plus:

Example with python script

Create a Python script that uses pymmcore-plus to perform some operations, such as instantiating a CMMCorePlus object, loading a configuration, and performing some task such as running a multi-dimensional acquisition. Save this script somewhere (we'll call it example.py) and profile it with py-spy:

Note: on macOS, py-spy requires sudo access

sudo py-spy record -o profile.json -f speedscope -r 1000 -- python example.py
  • the -o profile.json flag specifies the output file name
  • the -f speedscope flag specifies the output format. Here, we use the speedscope format, which can be viewed in the Speedscope web viewer.
  • the -r 1000 flag specifies the sampling rate (in Hz). The default is 100 Hz, so if you want more detailed profiling, you can increase this number as we've done here.
  • the -- separates the py-spy arguments from the Python script to be profiled

Example using mmcore run

If you are profiling a standard MDASequence acquisition, you can use the mmcore run command line tool to run the acquisition and profile it with py-spy. First, create a json/yaml file representing the MDASequence acquisition you want to run:

from useq import MDASequence

seq = MDASequence(
    channels=["DAPI", {"config": "FITC", "exposure": 1}],
    time_plan={"interval": 2, "loops": 5},
    z_plan={"range": 4, "step": 0.5},
    axis_order="tpcz",
)
seq_json = seq.model_dump_json(exclude_unset=True)

with open("mda_seq.json", "w") as f:
    f.write(seq_json)

Then, run the acquisition with mmcore run and profile it with py-spy:

sudo py-spy record -o profile.json -f speedscope -r 1000 -- mmcore run mda_seq.json --config your_mm_config.cfg
  • the -o, -f, -r, and -- flags are the same as in the previous example
  • the mmcore run command runs the acquisition specified in the json file (it doesn't save any data)
  • the --config flag specifies the path to the MMCore configuration file defining your microscope.

Example attaching to a running process

If you have a running Python process that you want to profile, you can attach py-spy to it using the -p flag:

sudo py-spy record -o profile.json -f speedscope -r 1000 -p <pid>

Analyzing the profile

Once you have a profile, you can view it in the Speedscope web viewer.

Open https://www.speedscope.app/ and drag the profile file into the window.

See the Speedscope documentation for more information. The "Sandwich" view is particularly useful for identifying slow functions and hot paths in your code:

Speedscope Sandwich View

Feel free to open an issue with your profile.json file if you need help interpreting the results.