Repository: ipython/ipython

Issue:
When %matplotlib inline is ON gc.collect() runs on every cell execution
Hi,

I'm not sure if it is the best place to put this issue. I'm working on an IPython notebook with a lot of objects loaded: `len(gc.get_objects())` is about 5M. Everything works fine until an inline plot is drawn. Drawing itself is fast, but then there is a delay (5-10s). The delay is caused by `gc.collect()` executed by matplotlib: https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/_pylab_helpers.py#L93. After the first plot the notebook becomes slow for all cells (even without inline plots). When interrupting the kernel (for cells without plots) I can see the same `gc.collect()` happening inside matplotlib.

As a workaround I'm monkey-patching `gc.collect` to be no-op, but this is clearly a hack.

It is `gc.collect()` call in matplotlib code that makes things slow, but why does IPython notebook need to call matplotlib code for cells without charts?



Discussion:
IPython makes matplotlib calls at the end of each cell to see if there are plots to draw. Can you tell what problematic call is being made (full stack up to an IPython call would be ideal) in a cell with no plots?

Here is a stacktrace for other cells (stacktrace for plots is the same):

```
Traceback (most recent call last):

  File "/Users/kmike/envs/scraping/lib/python2.7/site-packages/IPython/kernel/zmq/ipkernel.py", line 389, in execute_request
    shell.run_cell(code, store_history=store_history, silent=silent)

  File "/Users/kmike/envs/scraping/lib/python2.7/site-packages/IPython/core/interactiveshell.py", line 2743, in run_cell
    self.events.trigger('post_execute')

  File "/Users/kmike/envs/scraping/lib/python2.7/site-packages/IPython/core/events.py", line 82, in trigger
    func(*args, **kwargs)

  File "/Users/kmike/envs/scraping/lib/python2.7/site-packages/IPython/kernel/zmq/pylab/backend_inline.py", line 118, in flush_figures
    return show(True)

  File "/Users/kmike/envs/scraping/lib/python2.7/site-packages/IPython/kernel/zmq/pylab/backend_inline.py", line 47, in show
    matplotlib.pyplot.close('all')

  File "/Users/kmike/envs/scraping/src/matplotlib/lib/matplotlib/pyplot.py", line 517, in close
    _pylab_helpers.Gcf.destroy_all()

  File "/Users/kmike/envs/scraping/src/matplotlib/lib/matplotlib/_pylab_helpers.py", line 93, in destroy_all
    gc.collect()

KeyboardInterrupt
```

So it looks like the gc call wouldn't happen if we check whether there are any figures to close before calling `close('all')` (same goes for matplotlib's own destroy_all call).

Yes, it should fix it for non-plotting cells. I've fired a related issue to the matplotlib bug tracker.

btw, it's just amazing how fast are you responding to tickets here :+1:


Resolving patch:
diff --git a/IPython/kernel/zmq/pylab/backend_inline.py b/IPython/kernel/zmq/pylab/backend_inline.py
--- a/IPython/kernel/zmq/pylab/backend_inline.py
+++ b/IPython/kernel/zmq/pylab/backend_inline.py
@@ -1,30 +1,19 @@
 """A matplotlib backend for publishing figures via display_data"""
-#-----------------------------------------------------------------------------
-#       Copyright (C) 2011 The IPython Development Team
-#
-#  Distributed under the terms of the BSD License.  The full license is in
-#  the file COPYING, distributed as part of this software.
-#-----------------------------------------------------------------------------
-
-#-----------------------------------------------------------------------------
-# Imports
-#-----------------------------------------------------------------------------
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
 from __future__ import print_function
 
-# Third-party imports
 import matplotlib
 from matplotlib.backends.backend_agg import new_figure_manager, FigureCanvasAgg # analysis: ignore
 from matplotlib._pylab_helpers import Gcf
 
-# Local imports
 from IPython.core.getipython import get_ipython
 from IPython.core.display import display
 
 from .config import InlineBackend
 
-#-----------------------------------------------------------------------------
-# Functions
-#-----------------------------------------------------------------------------
 
 def show(close=None):
     """Show all figures as SVG/PNG payloads sent to the IPython clients.
@@ -43,7 +32,9 @@ def show(close=None):
             display(figure_manager.canvas.figure)
     finally:
         show._to_draw = []
-        if close:
+        # only call close('all') if any to close
+        # close triggers gc.collect, which can be slow
+        if close and Gcf.get_all_fig_managers():
             matplotlib.pyplot.close('all')
 
 


Test patch: