Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Bug]: Possible performance issue with _LazyTickList #28908

@timhoffm

Description

@timhoffm

Bug summary

The descriptor seems to get called twice and thus the expensive instance._get_tick(major=True) is executed twice. Ping @anntzer, who helped craft the implementation.

Code for reproduction

When adding the following into _LazyTickList.__get__

            if self._major:

                # <snip>
                import traceback, sys
                print(f"\n*** Initializing major ticks on {type(instance)} **\n")
                traceback.print_stack(file=sys.stdout, limit=6)
                # </snip>

                instance.majorTicks = []
                tick = instance._get_tick(major=True)
                instance.majorTicks.append(tick)
                return instance.majorTicks

we see that it is called twice per Axis:

*** Initializing major ticks on <class 'matplotlib.axis.XAxis'> **

  File "/home/tim/git/matplotlib/lib/matplotlib/axes/_base.py", line 1399, in clear
    self.__clear()
  File "/home/tim/git/matplotlib/lib/matplotlib/axes/_base.py", line 1315, in __clear
    self.grid(False)  # Disable grid on init to use rcParameter
  File "/home/tim/git/matplotlib/lib/matplotlib/axes/_base.py", line 3295, in grid
    self.xaxis.grid(visible, which=which, **kwargs)
  File "/home/tim/git/matplotlib/lib/matplotlib/axis.py", line 1726, in grid
    self.set_tick_params(which='major', **gridkw)
  File "/home/tim/git/matplotlib/lib/matplotlib/axis.py", line 984, in set_tick_params
    for tick in self.majorTicks:
  File "/home/tim/git/matplotlib/lib/matplotlib/axis.py", line 549, in __get__
    traceback.print_stack(file=sys.stdout, limit=6)

*** Initializing major ticks on <class 'matplotlib.axis.XAxis'> **

  File "/home/tim/git/matplotlib/lib/matplotlib/axes/_base.py", line 1315, in __clear
    self.grid(False)  # Disable grid on init to use rcParameter
  File "/home/tim/git/matplotlib/lib/matplotlib/axes/_base.py", line 3295, in grid
    self.xaxis.grid(visible, which=which, **kwargs)
  File "/home/tim/git/matplotlib/lib/matplotlib/axis.py", line 1726, in grid
    self.set_tick_params(which='major', **gridkw)
  File "/home/tim/git/matplotlib/lib/matplotlib/axis.py", line 984, in set_tick_params
    for tick in self.majorTicks:
  File "/home/tim/git/matplotlib/lib/matplotlib/axis.py", line 553, in __get__
    instance.majorTicks.append(tick)
  File "/home/tim/git/matplotlib/lib/matplotlib/axis.py", line 549, in __get__
    traceback.print_stack(file=sys.stdout, limit=6)

[... same repeated for YAxis]

Looking at the second traceback it seems that the line instance.majorTicks.append(tick) re-triggers the descriptor, even though we have previously set instance.majorTicks = []. I would have expected that at the time, the name instance.majorTicks is already re-bound to the list (which is sort of the purpose of the init-empty-and-append acrobatics - see the code comment above). But then again, this is higher magic and we might be hitting some implementation details of descriptors.

This observation may have two implications:

  • We're apparently running the expensive instance._get_tick(major=True) twice. This should be fixed.
  • It may be that init-empty-and-append acrobatics does not fulfill its intended purpose of providing instance.majorTicks to the implementation of _get_tick.

Possible alternative

have a dummy empty list for _get_tick - I assume it's anyway only trying to read and not modify or hold references.
And then create a new list with the tick. This prevents read-access to instance.majorTicks and re-calling the descriptor. i.e. replace

                instance.majorTicks = []
                tick = instance._get_tick(major=True)
                instance.majorTicks.append(tick)
                return instance.majorTicks

by

                instance.majorTicks = []
                tick = instance._get_tick(major=True)
                instance.majorTicks = [tick]
                return instance.majorTicks

Not sure how that works when _get_tick accesses majorTicks but it should not make it worse there, and we improve inside __get__.

Performance measurement:

While performance measurement is a bit tricky, I think fair timings are

before (ms) after (ms) change
plt.subplots() 38±2 31±1 -18%
plt.subplots(10, 10) 1420±13 1063±7 -25%

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions