Calculation and rendering ------------------------- This section considers the time taken to calculate contours and render them using `Matplotlib`_ on a 900x900 pixel canvas (using the Agg backend) and save them to PNG files. Contour lines ^^^^^^^^^^^^^ .. image:: ../_static/lines_simple_1000_render_light.svg :class: only-light .. image:: ../_static/lines_simple_1000_render_dark.svg :class: only-dark For the ``simple`` dataset above the performance of :ref:`serial` for contour lines is the same regardless of :class:`~.LineType`. It is slightly faster than :ref:`mpl2005` and significantly faster than :ref:`mpl2014` with a speedup of 1.3-1.4. Comparing these times with the calculation-only times of the previous section shows that the rendering time here is only about 120 ms so there is a fairly even split between calculation and rendering time for :ref:`serial`. .. image:: ../_static/lines_random_1000_render_light.svg :class: only-light .. image:: ../_static/lines_random_1000_render_dark.svg :class: only-dark For the ``random`` dataset above the performance of :ref:`serial` varies significantly by :class:`~.LineType`. For ``LineType.SeparateCode`` all three algorithms take about the same time. The calculation time for these is about 1.2 seconds, so here the rendering takes almost 90% of the total time. This is much more than for ``simple`` as the rendering is much more complicated. The :class:`~.LineType` that do not include `Matplotlib`_ kind codes are slower. You must generate these codes yourself or allow `Matplotlib`_ to generate them for you, either way is slower than asking ``contourpy`` to generate them for you in C++. ``LineType.ChunkCombinedCode`` is significantly faster than ``LineType.SeparateCode`` with a speedup of 1.9-2.2. This means that use of the :ref:`serial` algorithm with ``LineType.ChunkCombinedCode`` could halve the calculation and rendering time in `Matplotlib`_ for such complicated datasets. The benefit here is in just having two `NumPy`_ arrays per chunk, one for points and one for codes, which are ultimately used by `Matplotlib`_'s Agg renderer. Compare this with ``LineType.SeparateCode`` which passes 850 thousand pairs of `NumPy`_ arrays that need to be iterated over. Filled contours ^^^^^^^^^^^^^^^ .. image:: ../_static/filled_simple_1000_render_light.svg :class: only-light .. image:: ../_static/filled_simple_1000_render_dark.svg :class: only-dark As usual, for the ``simple`` dataset above the performance of :ref:`serial` for filled contours is the same regardless of :class:`~.FillType`. It is about the same as :ref:`mpl2005` and significantly faster than :ref:`mpl2014` with a speedup of 1.5. As with ``lines`` the rendering time here is only about 110 ms so there is a fairly even split between calculation and rendering time for :ref:`serial`. .. image:: ../_static/filled_random_1000_render_light.svg :class: only-light .. image:: ../_static/filled_random_1000_render_dark.svg :class: only-dark For the ``random`` dataset above the performance of :ref:`serial` varies significantly by :class:`~.FillType`. For ``FillType.OuterCode`` it is faster than :ref:`mpl2014` by about 10%. The calculation for :ref:`serial` here was about 1.7 s so the rendering takes about four times as long as the calculation. Three of the other :ref:`serial` :class:`~.FillType` take longer because they either have to create `Matplotlib`_ kind codes anyway, in a less efficient way than allowing ``contourpy`` to do so in C++, or in the case of ``FillType.ChunkCombinedCodeOffset`` by breaking up the large points and codes arrays into many smaller arrays, one per polygon (outer plus holes). ``FillType.ChunkCombinedCode`` and ``FillType.ChunkCombinedOffset`` are significantly faster than ``FillType.OuterCode`` with a speedup of 2.6-3.0 compared to :ref:`serial` and 3.0-3.2 compared to :ref:`mpl2014`. Again this only has to send two `NumPy`_ arrays to `Matplotlib`_ for rendering rather than 850 thousand pairs of them. Care would be needed in switching to use ``FillType.ChunkCombinedCode`` in `Matplotlib`_. Rendering of filled polygons is much more computationally expensive than lines because with lines each is independent and they can just be rendered in turn, whereas with polygons all of the boundaries need to be processed at the same time so that each hole is associated with its corresponding outer boundary. With ``FillType.OuterCode`` each polygon (outer plus holes) is passed to the renderer separately, so the renderers task is easy. But with ``FillType.ChunkCombinedCode`` all of a chunk's boundaries are passed together. Typically such a renderer uses a sweep line algorithm which is O(N log N) where N is the number of points, so the sweep lines would be faster overall if sent a large number of small arrays rather than one, but this speed advantage is much less than the disadvantage of having to create and manipulate a large number of `NumPy`_ arrays. .. warning:: Although ``FillType.ChunkCombinedCode`` is empirically a good choice for the Agg renderer it may not be so good for other backends. If `Matplotlib`_ renderers could accept ``FillType.ChunkCombinedCodeOffset`` then we would have the best of both worlds. Only three arrays would need to passed per chunk, but the offsets could be used to divide up the points and codes into individual polygons to be rendered separately. If this could be done without creating extra `NumPy`_ arrays, e.g. at the C++ level, this would be very fast. .. note:: For these rendering benchmarks, ``FillType.ChunkCombinedCodeOffset`` is converted into the same form as ``FillType.OuterCode``, i.e. separate arrays for each outer boundary. It could instead be converted into the same form as ``FillType.ChunkCombinedCode`` with all of each chunk's boundaries grouped together, and it would be much faster. But if the outer offsets are going to be ignored then they may as well not be calculated so ``FillType.ChunkCombinedCode`` may as well be used instead.