Last active
October 30, 2017 04:58
-
-
Save afvincent/fe0618837d36f668bc885451b17b6bce to your computer and use it in GitHub Desktop.
Benchmark for matplotlib #9598
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
""" | |
Memo for pprofile: | |
pprofile --out cachegrind.out benchmark_cleverer_legend.py >> profiling.log | |
""" | |
from __future__ import division, print_function, unicode_literals | |
import numpy as np | |
import matplotlib.pyplot as plt | |
import matplotlib.legend as mlegend | |
import mock # to monkey-patch the relevant method of ``Legend`` | |
import pprofile # === pprofile version === | |
#from line_profiler import LineProfiler # === line_profiler version === | |
from matplotlib.patches import Polygon | |
from matplotlib.collections import PatchCollection | |
# Import what is needed in ``_auto_legend_data`` | |
from matplotlib.lines import Line2D | |
from matplotlib.patches import Rectangle, Patch | |
#%% | |
# The methods that one wants to profile | |
def _former_auto_legend_data(self): | |
assert self.isaxes | |
ax = self.parent | |
bboxes = [] | |
lines = [] | |
offsets = [] | |
for handle in ax.lines: | |
assert isinstance(handle, Line2D) | |
path = handle.get_path() | |
trans = handle.get_transform() | |
tpath = trans.transform_path(path) | |
lines.append(tpath) | |
for handle in ax.patches: | |
assert isinstance(handle, Patch) | |
if isinstance(handle, Rectangle): | |
transform = handle.get_data_transform() | |
bboxes.append(handle.get_bbox().transformed(transform)) | |
else: | |
transform = handle.get_transform() | |
# The former strategy | |
bboxes.append(handle.get_path().get_extents(transform)) | |
for handle in ax.collections: | |
transform, transOffset, hoffsets, paths = handle._prepare_points() | |
if len(hoffsets): | |
for offset in transOffset.transform(hoffsets): | |
offsets.append(offset) | |
try: | |
vertices = np.concatenate([l.vertices for l in lines]) | |
except ValueError: | |
vertices = np.array([]) | |
return [vertices, bboxes, lines, offsets] | |
def _new_auto_legend_data(self): | |
assert self.isaxes | |
ax = self.parent | |
bboxes = [] | |
lines = [] | |
offsets = [] | |
for handle in ax.lines: | |
assert isinstance(handle, Line2D) | |
path = handle.get_path() | |
trans = handle.get_transform() | |
tpath = trans.transform_path(path) | |
lines.append(tpath) | |
for handle in ax.patches: | |
assert isinstance(handle, Patch) | |
if isinstance(handle, Rectangle): | |
transform = handle.get_data_transform() | |
bboxes.append(handle.get_bbox().transformed(transform)) | |
else: | |
transform = handle.get_transform() | |
# The suggested new strategy | |
tpath = transform.transform_path(handle.get_path()) | |
lines.append(tpath) | |
for handle in ax.collections: | |
transform, transOffset, hoffsets, paths = handle._prepare_points() | |
if len(hoffsets): | |
for offset in transOffset.transform(hoffsets): | |
offsets.append(offset) | |
try: | |
vertices = np.concatenate([l.vertices for l in lines]) | |
except ValueError: | |
vertices = np.array([]) | |
return [vertices, bboxes, lines, offsets] | |
#%% | |
# Helper functions for plotting | |
def draw_hist(data, ax=None, **kwargs): | |
if ax is None: | |
fig_label = "histogram" | |
plt.close(fig_label) | |
fig, ax = plt.subplots(num=fig_label) | |
_bins = np.linspace(data.min() - 0.01, data.max() + 0.01, len(data)//10) | |
kwargs.setdefault("bins", _bins) | |
ax.hist(data, **kwargs) | |
return ax | |
def draw_patches(patch_list, ax=None): | |
if ax is None: | |
fig_label = "patches" | |
plt.close(fig_label) | |
fig, ax = plt.subplots(num=fig_label) | |
for _p in patch_list: | |
ax.add_patch(_p) | |
return ax | |
def draw_collection(coll, ax=None): | |
if ax is None: | |
fig_label = "collection" | |
plt.close(fig_label) | |
fig, ax = plt.subplots(num=fig_label) | |
ax.add_collection(coll) | |
return ax | |
def draw_polygon(prng, N_edge_max, extent, **kwargs): | |
"""Return a random Polygon with at most *N_edge_max* edges, | |
all of them in the *extent* (xmin, xmax, ymin, ymax). | |
""" | |
xmin, xmax, ymin, ymax = extent | |
N_edges = prng.randint(3, N_edge_max) | |
x_values = (xmax - xmin)*prng.random_sample(size=(N_edges, 1)) + xmin | |
y_values = (ymax - ymin)*prng.random_sample(size=(N_edges, 1)) + ymin | |
return Polygon(np.hstack((x_values, y_values)), **kwargs) | |
#%% | |
# It's plotting time! | |
N_iterations = 50 # Repeat the plots several times to average the profiling | |
N_hist_samples = 50000 | |
N_patches = 500 # Use a multiple of 4 if one wants this exact amount | |
N_edge_max = 20 # >= 3 (please) | |
for mth, lbl in ( | |
(_former_auto_legend_data, "former"), (_new_auto_legend_data, "new")): | |
with mock.patch.object(mlegend.Legend, "_auto_legend_data", mth): | |
prng = np.random.RandomState(390423) # same seed in both cases | |
print("\n=== Benchmarking the {0} auto legend locator ===".format(lbl)) | |
# Prepare the figure and the relevant axes | |
fig_label = "benchmark_of_{0}_method".format(lbl) | |
plt.close(fig_label) | |
fig, (ax0, ax1, ax2) = plt.subplots( | |
ncols=3, num=fig_label, figsize=(12.8, 4.8)) | |
# Set up a profiler instance for each type of plot | |
# === line_profiler version === | |
#lp0 = LineProfiler() | |
#ax0_lgd_wrapper = lp0(ax0.legend) | |
#lp1 = LineProfiler() | |
#ax1_lgd_wrapper = lp1(ax1.legend) | |
#lp2 = LineProfiler() | |
#ax2_lgd_wrapper = lp2(ax2.legend) | |
# === pprofile version === | |
prof0 = pprofile.Profile() | |
prof1 = pprofile.Profile() | |
prof2 = pprofile.Profile() | |
for idx in range(N_iterations): | |
# Histogram with a high amount of bins | |
data = prng.random_sample(size=N_hist_samples) | |
data[data < 0.1] = 0.01 | |
data[data > 0.9] = 0.99 | |
ax = ax0 | |
ax.clear() | |
ax = draw_hist(data, ax=ax, histtype="step", label="Data") | |
# Axes with a significant amount of ``Patch`` (distinct) instances | |
# NB: target a centered legend as it's the last possible location. | |
patch_list = [] | |
_N4 = N_patches // 4 | |
for extent in ([0.0, 1.0, 0.8, 1.0], [0.0, 0.2, 0.0, 1.0], | |
[0.0, 1.0, 0.0, 0.2], [0.8, 1.0, 0.0, 1.0]): | |
patch_list += [ | |
draw_polygon(prng, N_edge_max, extent) for _ in range(_N4)] | |
ax = ax1 | |
ax.clear() | |
ax = draw_patches(patch_list, ax=ax) | |
ax.plot([0.0, 1.0], [0.9, 0.9], color="tab:red", label="A segment") | |
# Axes using a ``PatchCollection`` instance instead | |
patch_coll = PatchCollection(patch_list, color="tab:blue") | |
ax = ax2 | |
ax.clear() | |
ax = draw_collection(patch_coll, ax=ax) | |
ax.plot([0.0, 1.0], [0.9, 0.9], color="tab:red", label="A segment") | |
# Update the canvas with the auto legend locator | |
# === line_profiler version === | |
#for ax_legend_wrapper in ( | |
# ax0_lgd_wrapper, ax1_lgd_wrapper, ax2_lgd_wrapper): | |
# ax_legend_wrapper(loc="best") | |
# === pprofile version === | |
for prof, ax in ((prof0, ax0), (prof1, ax1), (prof2, ax2)): | |
with prof(): | |
ax.legend(loc="best") | |
fig.canvas.draw() | |
if (idx % 10) == 0: | |
fig.savefig(fig.get_label() + "_{:03d}.png".format(idx)) | |
for plot_type, prof in ( | |
("hist.", prof0), ("patch.", prof1), ("coll.", prof2)): | |
print("\nProfile for the '{0}' plot:".format(plot_type)) | |
prof.print_stats() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# The full text log is ~ 25 MiB and the cachegrind.out file is similarly ~ 20 MiB... | |
# Here is a very minimalistic excerpt from the full text log. | |
=== Benchmarking the former auto legend locator === | |
Profile for the 'hist.' plot: | |
Total duration: 5.67146s | |
Line #| Hits| Time| Time per hit| %|Source code | |
------+----------+-------------+-------------+-------+----------- | |
234| 0| 0| 0| 0.00%| ax.legend(loc="best") | |
(call)| 50| 5.65752| 0.11315| 99.75%|# /home/adrien/matplotlib-git/lib/matplotlib/axes/_axes.py:296 legend | |
Profile for the 'patch.' plot: | |
Total duration: 25.4753s | |
Line #| Hits| Time| Time per hit| %|Source code | |
------+----------+-------------+-------------+-------+----------- | |
234| 0| 0| 0| 0.00%| ax.legend(loc="best") | |
(call)| 50| 25.4626| 0.509251| 99.95%|# /home/adrien/matplotlib-git/lib/matplotlib/axes/_axes.py:296 legend | |
Profile for the 'coll.' plot: | |
Total duration: 7.6444s | |
Line #| Hits| Time| Time per hit| %|Source code | |
------+----------+-------------+-------------+-------+----------- | |
234| 0| 0| 0| 0.00%| ax.legend(loc="best") | |
(call)| 50| 7.63151| 0.15263| 99.83%|# /home/adrien/matplotlib-git/lib/matplotlib/axes/_axes.py:296 legend | |
=== Benchmarking the new auto legend locator === | |
Profile for the 'hist.' plot: | |
Total duration: 5.72455s | |
Line #| Hits| Time| Time per hit| %|Source code | |
------+----------+-------------+-------------+-------+----------- | |
234| 0| 0| 0| 0.00%| ax.legend(loc="best") | |
(call)| 50| 5.71054| 0.114211| 99.76%|# /home/adrien/matplotlib-git/lib/matplotlib/axes/_axes.py:296 legend | |
Profile for the 'patch.' plot: | |
Total duration: 25.8004s | |
Line #| Hits| Time| Time per hit| %|Source code | |
------+----------+-------------+-------------+-------+----------- | |
234| 0| 0| 0| 0.00%| ax.legend(loc="best") | |
(call)| 50| 25.7874| 0.515749| 99.95%|# /home/adrien/matplotlib-git/lib/matplotlib/axes/_axes.py:296 legend | |
Profile for the 'coll.' plot: | |
Total duration: 7.71593s | |
Line #| Hits| Time| Time per hit| %|Source code | |
------+----------+-------------+-------------+-------+----------- | |
234| 0| 0| 0| 0.00%| ax.legend(loc="best") | |
(call)| 50| 7.70293| 0.154059| 99.83%|# /home/adrien/matplotlib-git/lib/matplotlib/axes/_axes.py:296 legend |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment