From c94c730d6562af79c2b62f20415e3a8b0d8fca6d Mon Sep 17 00:00:00 2001 From: Pierre Haessig Date: Sat, 24 Nov 2012 18:41:27 +0100 Subject: [PATCH] fix rendering slowdown of big Lines (issue #1256) About the fix: Line2D.draw method now *returns immediately*, if the Line is invisible (get_visible() returns False). In particular, the `self.recache` and `self._transform_path` calls are short-circuited. In addition to the bugfix: * adds a test case for lines rendering speed (issue #1256) * adds a README in `matplotlib.tests` to redirect people to the testing section of the development guide. --- lib/matplotlib/__init__.py | 1 + lib/matplotlib/lines.py | 5 +-- lib/matplotlib/tests/README | 9 +++++ lib/matplotlib/tests/test_lines.py | 53 ++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 lib/matplotlib/tests/README create mode 100644 lib/matplotlib/tests/test_lines.py diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 1334c8596209..b974c85314a3 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1097,6 +1097,7 @@ def tk_window_focus(): 'matplotlib.tests.test_figure', 'matplotlib.tests.test_image', 'matplotlib.tests.test_legend', + 'matplotlib.tests.test_lines', 'matplotlib.tests.test_mathtext', 'matplotlib.tests.test_mlab', 'matplotlib.tests.test_patches', diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 1b64165d4bc2..631885594a91 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -484,6 +484,9 @@ def _is_sorted(self, x): @allow_rasterization def draw(self, renderer): + """draw the Line with `renderer` unless visiblity is False""" + if not self.get_visible(): return + if self._invalidy or self._invalidx: self.recache() self.ind_offset = 0 # Needed for contains() method. @@ -498,8 +501,6 @@ def draw(self, renderer): transformed_path = self._get_transformed_path() - if not self.get_visible(): return - renderer.open_group('line2d', self.get_gid()) gc = renderer.new_gc() self._set_gc_clip(gc) diff --git a/lib/matplotlib/tests/README b/lib/matplotlib/tests/README new file mode 100644 index 000000000000..0f243229a0c6 --- /dev/null +++ b/lib/matplotlib/tests/README @@ -0,0 +1,9 @@ +About Matplotlib Testing Infrastructure +--------------------------------------- + +Information on the testing infrastructure is provided in +the Testing section of the Matplotlib Developers’ Guide: + +* http://matplotlib.org/devel/testing.html +* /doc/devel/coding_guide.rst (equivalent, but in reST format) + diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py new file mode 100644 index 000000000000..6a7a739b9f12 --- /dev/null +++ b/lib/matplotlib/tests/test_lines.py @@ -0,0 +1,53 @@ +""" +Tests specific to the lines module. +""" + +from nose.tools import assert_true +from timeit import repeat +import numpy as np +import matplotlib as mpl +import matplotlib.pyplot as plt +from matplotlib.testing.decorators import cleanup + +@cleanup +def test_invisible_Line_rendering(): + """ + Github issue #1256 identified a bug in Line.draw method + + Despite visibility attribute set to False, the draw method was not + returning early enough and some pre-rendering code was executed + though not necessary. + + Consequence was an excessive draw time for invisible Line instances + holding a large number of points (Npts> 10**6) + """ + # Creates big x and y data: + N = 10**7 + x = np.linspace(0,1,N) + y = np.random.normal(size=N) + + # Create a plot figure: + fig = plt.figure() + ax = plt.subplot(111) + + # Create a "big" Line instance: + l = mpl.lines.Line2D(x,y) + l.set_visible(False) + # but don't add it to the Axis instance `ax` + + # [here Interactive panning and zooming is pretty responsive] + # Time the canvas drawing: + t_no_line = min(repeat(fig.canvas.draw, number=1, repeat=3)) + # (gives about 25 ms) + + # Add the big invisible Line: + ax.add_line(l) + + # [Now interactive panning and zooming is very slow] + # Time the canvas drawing: + t_unvisible_line = min(repeat(fig.canvas.draw, number=1, repeat=3)) + # gives about 290 ms for N = 10**7 pts + + slowdown_factor = (t_unvisible_line/t_no_line) + slowdown_threshold = 2 # trying to avoid false positive failures + assert_true(slowdown_factor < slowdown_threshold)