Skip to content

Commit

Permalink
Speed up navigation generation for "-M html" builder
Browse files Browse the repository at this point in the history
This was pair-programmed with @ax-lothas

As in the standalone html builder the navigation is flattened out
for every single html page, the code needs to create a specialized toctree
for every html page.

Previously this was done by deep-copying the complete navigation toctree
and then stripping out the parts not needed on the particular page.

With this change the code only (deep)-copies the needed parts of the toctree
avoiding unnecessary copying+throwing-away

The performance improvements seems to be smaller for smaller page counts and
get bigger the more pages are involved:

+----------------------------------+-----------+-----------+-------------------+
|         830 Source Files         |   5.3.0   |  5.3.0mod |       Ratio       |
+----------------------------------+-----------+-----------+-------------------+
|          SingleThreaded          |  49.137 s |  43.517 s | 0.885625903087287 |
| Parallel (auto, 16 logical CPUs) |  13.334 s |  12.849 s | 0.963626818659067 |
+----------------------------------+-----------+-----------+-------------------+

+----------------------------------+-----------+-----------+-------------------+
|        6166 Source Files         |   5.3.0   |  5.3.0mod |       Ratio       |
+----------------------------------+-----------+-----------+-------------------+
|          SingleThreaded          | 907.497 s | 521.945 s | 0.575147906825036 |
| Parallel (auto, 16 logical CPUs) | 195.400 s | 150.139 s | 0.768367451381781 |
+----------------------------------+-----------+-----------+-------------------+
  • Loading branch information
hofmandl1 committed Nov 23, 2022
1 parent cd3f2e4 commit 07d965c
Showing 1 changed file with 39 additions and 6 deletions.
45 changes: 39 additions & 6 deletions sphinx/environment/adapters/toctree.py
@@ -1,6 +1,6 @@
"""Toctree adapter for sphinx.environment."""

from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Tuple, cast
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Tuple, TypeVar, cast

from docutils import nodes
from docutils.nodes import Element, Node
Expand Down Expand Up @@ -158,10 +158,12 @@ def _entries_from_toctree(toctreenode: addnodes.toctree, parents: List[str],
location=ref, type='toc', subtype='circular')
continue
refdoc = ref
toc = self.env.tocs[ref].deepcopy()
maxdepth = self.env.metadata[ref].get('tocdepth', 0)
toc = self.env.tocs[ref]
if ref not in toctree_ancestors or (prune and maxdepth > 0):
self._toctree_prune(toc, 2, maxdepth, collapse)
toc = self._toctree_copy(toc, 2, maxdepth, collapse)
else:
toc = toc.deepcopy()
process_only_nodes(toc, builder.tags)
if title and toc.children and len(toc.children) == 1:
child = toc.children[0]
Expand Down Expand Up @@ -281,9 +283,41 @@ def get_toctree_ancestors(self, docname: str) -> List[str]:
d = parent[d]
return ancestors

ET = TypeVar('ET', bound=Element)

def _toctree_copy(self, node: ET, depth: int, maxdepth: int, collapse: bool = False) -> ET:
"""Utility: deep-copy a TOC but omit things with the semantics of ._toctree_prune.
!!! MUST be kept consistent with ._toctree_prune !!!
"""
copy = node.copy()
for subnode in node.children:
if isinstance(subnode, (addnodes.compact_paragraph,
nodes.list_item)):
# for <p> and <li>, just recurse
copy.append(self._toctree_copy(subnode, depth, maxdepth, collapse))
elif isinstance(subnode, nodes.bullet_list):
# for <ul>, determine if the depth is too large or if the
# entry is to be collapsed
if maxdepth > 0 and depth > maxdepth:
pass
else:
# cull sub-entries whose parents aren't 'current'
if (collapse and depth > 1 and
'iscurrent' not in subnode.parent):
pass
else:
# recurse on visible children
copy.append(self._toctree_copy(subnode, depth + 1, maxdepth, collapse))
else:
copy.append(subnode.deepcopy())
return copy

def _toctree_prune(self, node: Element, depth: int, maxdepth: int, collapse: bool = False
) -> None:
"""Utility: Cut a TOC at a specified depth."""
"""Utility: Cut a TOC at a specified depth.
!!! MUST be kept consistent with ._toctree_copy !!!"""
for subnode in node.children[:]:
if isinstance(subnode, (addnodes.compact_paragraph,
nodes.list_item)):
Expand All @@ -307,8 +341,7 @@ def get_toc_for(self, docname: str, builder: "Builder") -> Node:
"""Return a TOC nodetree -- for use on the same page only!"""
tocdepth = self.env.metadata[docname].get('tocdepth', 0)
try:
toc = self.env.tocs[docname].deepcopy()
self._toctree_prune(toc, 2, tocdepth)
toc = self._toctree_copy(self.env.tocs[docname], 2, tocdepth)
except KeyError:
# the document does not exist anymore: return a dummy node that
# renders to nothing
Expand Down

0 comments on commit 07d965c

Please sign in to comment.