FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Refactor of `axis.py`/ticking should have large performance gains · Issue #5665 · matplotlib/matplotlib · GitHub

Repository navigation

Refactor of axis.py/ticking should have large performance gains #5665

Open

Description

Right now, each Axis has a two lists of Tick objects (one major, one minor), each of which has 3 Line objects (for left tick, right tick and gridline) and 2 Text objects (for main and secondary label). Constructing Line objects is rather heavy and expensive. Refactoring this code so that each Axis had 3 LineCollection objects instead where only the points of the ticks needs to be updated, sharing all of the line style information, should be much faster.

On the following simple benchmark:

plt.subplots(8, 8)
plt.savefig('test.png')

25% of the time is spent constructing the Line objects. The suggested change above should reduce the number of Line objects from 3*N (where N is the number of ticks, usually a dozen or so) to 3.

Activity

  1. added
    Difficulty: Mediumhttps://matplotlib.org/devdocs/devel/contribute.html#good-first-issues
    on Dec 11, 2015
  2. Tillsten commented on Dec 11, 2015

    Contributor

    I think i have a prototype doing that, if no one works on this before i will have a look after christmas. The whole axis code seems quite convoluted for what it does.

  3. timhoffm commented on Mar 28, 2018

    Member

    This looks like an interesting topic with some performance gain and not too difficult (once we've figured out the right structure). 😄
    I may embark on the topic. Some first thoughts:

    Current implementation:

    Axis
    - major: Ticker
    - minor: Ticker
    - majorticks: List[Tick]
    - minorticks: List[Tick]
    
    Ticker
    - locator: Locator
    - formatter: Formatter
    
    Tick
    - tick1line: Line
    - tick2line: Line
    - gridline: Line
    - label1: Text
    - label2: Text
    

    Possible alternative implementation (first try):

    Axis
    - majorticks: TickList
    - minorticks: TickList
    
    TickList
    - locator: Locator
    - formatter: Formatter
    - ticks: LineCollection
    - secondary_ticks: LineCollection
    - gridlines: LineCollection
    - ticklabels: List[Text]
    - secondary_ticklabels: List[Text]
    

    This feels significantly better and should be doable with reasonable backward compatibility (full compatibility may be difficult or at least require some extra work, if users really descend to the Artists of individual ticks). I'm not sure if and how much incompatibility we want to allow here.

    Structuring by major/minor and then by primary/secondary may not be the best option. One could swap the order so that

    Axis
    - ticks: AxisTicks
    - secondary_ticks: AxisTicks
    
    AxisTicks
    - major: LineCollection
    - minor: LineCollection
    ...
    

    This would potentially allow different ticks on the secondary axis. Also it could make it simpler to handle interactions between minor and major ticks (e.g. leave out minor ticks at the position of major ticks).

  4. added this to the milestone on Mar 28, 2018
  5. anntzer commented on Mar 28, 2018

    Contributor

    @timhoffm I had started giving a try to the idea (not pursuing it actively now), per https://github.com/anntzer/matplotlib/tree/_wip/newaxis (taking an approach of "delete everything and reimplement stuff on top of it"). Feel free to scavenge stuff from that.

    A few random notes:

    • LineCollections don't support markers, so if you use them for ticks each tick has to become a line itself, rather than being represented by a marker. Conversely, using a single line object to represent all ticks (what I've done right now) makes controlling properties of individual ticks close to impossible.
    • I think that it'll be very difficult to maintain backcompat on a project like that; however, a possibility (noted in this week's call) would be to have a new, separate Axes class that uses these new Axises (while also maintaining the user-facing plotting methods); one could then choose to use the new Axes class by (ab)using the projection system.
  6. jklymak commented on Mar 28, 2018

    Member

    This was discussed a bit during the call this week.

    In working on #10841, I found it pretty strange that the formatter only operated on one tick at a time, and the formatter ended up looping through all the ticks on the first tick and then caching the labels for all the ticks. So, a reorganization like this makes a lot of sense to me - I think there are more use cases where you want to treat the ticks as a list rather than being able to do something (?) to individual ticks.

  7. anntzer commented on Mar 28, 2018

    Contributor

    #5804 is possibly relevant re: formatter work too.

  8. efiring commented on Mar 28, 2018

    Member

    I suspect the need to manipulate individual ticks is extremely rare. It's possible that the tick handling pre-dated the introduction of collections.

  9. anntzer commented on Mar 28, 2018

    Contributor

    One case (not that I use it, but doesn't sounds too crazy) is https://matplotlib.org/examples/api/skewt.html, where you can see that for some of the slanted gridlines, the associated tick is not visible.

  10. timhoffm commented on Mar 28, 2018

    Member

    @anntzer Thanks for the example. It helps to have an idea what could all be needed. This needed a special implementation for XTick. The same solution for a future TickCollection (or whatever it's called) will probably need a special subclass of that.

    @ all: Generally, can we agree that in the default implementation all ticks of a kind (major/minor) will have the same properties? It's still always possible to tell the locator to leave out certain positions and draw something custom there yourself.

  11. anntzer commented on Mar 28, 2018

    Contributor

    I just realized that that specific example could in fact be made to work by clipping the x-ticks to a bounding box that bounds the horizontal direction but not the vertical one -- perhaps a better idea than manipulating tick visibility...).

  12. tacaswell commented on Mar 28, 2018

    Member

    I suspect we will need to aggressively shim the back compatibility on anything that is public API (and maybe even some private stuff). Users are extremely clever and find all the corner cases.

    separating the primary/secondary ticks is probably a good idea as that makes in easy(ier) to put different locators and tickers on each of them.

    It is probably worth going through the axis_grid implementation of ticks and axis objects carefully to understand what it does differently, what of it we want to pull up to mainline, and what lessons we should learn.

  13. anntzer commented on Mar 28, 2018

    Contributor

    ... actually playing with the clipbox won't work in the example above as labels will not be cropped out properly (they may only be half-clipped).

    @tacaswell If we move stuff to a separate "projection" (as proposed above) the new implementation would be strictly opt-in. It doesn't mean we should gratuitiously break things but given that the current proposals don't even really include the notion of "individual" ticks (everything goes into a Collection object), any shimming is going to be tricky too...

  14. tacaswell commented on Mar 28, 2018

    Member

    I responded not having read the rest of the discussion (because I did not reload my browser), sorry.

    Putting in it a separate projection is a good way to make progress and side-step back compatibility issues, but I worry that we will end up with a third full independent implementation of ticks and friends in the code base.

  15. 22 remaining items

  16. modified the milestones: v3.5.0, v3.6.0 on Sep 25, 2021
  17. modified the milestones: v3.6.0, v3.7.0 on Jul 8, 2022
  18. github-actions commented on Jan 29, 2024

    This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!

  19. timhoffm commented on Jan 29, 2024

    Member

    #5665 (comment) Still holds

  20. added
    keepItems to be ignored by the “Stale” Github Action
    and removed
    status: inactiveMarked by the “Stale” Github Action
    on Jan 29, 2024
  21. scottshambaugh commented on Jan 8, 2025

    Contributor

    Another datapoint from #29435 that even with plotted data, ticks operations are taking up ~half the draw time on basic 3D plots (profiled using py-spy):

  22. tacaswell commented on Jan 9, 2025

    Member
  23. timhoffm commented on Sep 21, 2025

    Member

    I just did some profiling today, because I was annoyed by the duration of the test suite. For

    fig, ax = plt.subplots()
    ax.plot([1, 3, 2])
    

    Basically all time goes into Axes.__init__, more specifically Axes.__clear. We more or less know that already from earlier, but to state this explicitly: Axes initialization is the limiting factor.

    Within Axes.__clear(), 30% goes into this line

    self.grid(False) # Disable grid on init to use rcParameter

    which in the end calls Axis.set_tick_params; and I suspect that this triggers tick creation.

  24. QuLogic commented on Sep 22, 2025

    Member

    These loops are likely triggers:

    if which in ['major', 'both']:
    self._major_tick_kw.update(kwtrans)
    for tick in self.majorTicks:
    tick._apply_params(**kwtrans)
    if which in ['minor', 'both']:
    self._minor_tick_kw.update(kwtrans)
    for tick in self.minorTicks:
    tick._apply_params(**kwtrans)

    but also probably this one:
    for child in self.majorTicks + self.minorTicks:
    child.set_clip_path(path, transform)

    I'm uncertain how to check if the descriptor is realized before accessing it. Actually, it looks like #16067 might lazify these two cases?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Difficulty: Mediumhttps://matplotlib.org/devdocs/devel/contribute.html#good-first-issuesPerformancekeepItems to be ignored by the “Stale” Github Actiontopic: ticks axis labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL