Skip to content

Commit

Permalink
Further improve markdown compiler performance
Browse files Browse the repository at this point in the history
Stop using the markdown() convenience function in the
CompileMarkdown.compile_string() method, which creates a new Markdown
object each time it is called.

Instead, create a new class, ThreadLocalMarkdown, which is a subclass of
threading.local.  This class has a single attribute, markdown, which is
a per-thread Markdown instance.  It also has a single method, convert(),
which converts the data using the Markdown instance's convert() method,
and then resets the Markdown instance's internal state.

Then in the CompileMarkdown.set_site() method, instantiate a new
ThreadLocalMarkdown instance (unless the markdown module is unavailable)
and assign it to the plugin's converter attribute.  In the
compile_string() method, use that ThreadLocalMarkdown instance to
perform the conversion.

The end result is one Markdown object created per thread, instead of one
for each markdown file compiled.
  • Loading branch information
living180 committed Feb 15, 2017
1 parent fd9a856 commit 91e5581
Showing 1 changed file with 24 additions and 8 deletions.
32 changes: 24 additions & 8 deletions nikola/plugins/compile/markdown/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@

import io
import os
import threading

try:
from markdown import markdown
from markdown import Markdown
except ImportError:
markdown = None # NOQA
Markdown = None # NOQA
nikola_extension = None
gist_extension = None
podcast_extension = None
Expand All @@ -43,41 +44,56 @@
from nikola.utils import makedirs, req_missing, write_metadata


class ThreadLocalMarkdown(threading.local):
"""Convert to markdown using per-thread Markdown objects."""

def __init__(self, extensions):
self.markdown = Markdown(extensions=extensions, output_format="html5")

def convert(self, data):
"""Convert data to HTML and reset internal state."""
result = self.markdown.convert(data)
self.markdown.reset()
return result


class CompileMarkdown(PageCompiler):
"""Compile Markdown into HTML."""

name = "markdown"
friendly_name = "Markdown"
demote_headers = True
extensions = []
site = None

def set_site(self, site):
"""Set Nikola site."""
super(CompileMarkdown, self).set_site(site)
self.config_dependencies = []
extensions = []
for plugin_info in self.get_compiler_extensions():
self.config_dependencies.append(plugin_info.name)
self.extensions.append(plugin_info.plugin_object)
extensions.append(plugin_info.plugin_object)
plugin_info.plugin_object.short_help = plugin_info.description

site_extensions = self.site.config.get("MARKDOWN_EXTENSIONS")
self.config_dependencies.append(str(sorted(site_extensions)))
self.extensions.extend(site_extensions)
extensions.extend(site_extensions)
if Markdown is not None:
self.converter = ThreadLocalMarkdown(extensions)

def compile_string(self, data, source_path=None, is_two_file=True, post=None, lang=None):
"""Compile Markdown into HTML strings."""
if markdown is None:
if Markdown is None:
req_missing(['markdown'], 'build this site (compile Markdown)')
if not is_two_file:
_, data = self.split_metadata(data)
output = markdown(data, self.extensions, output_format="html5")
output = self.converter.convert(data)
output, shortcode_deps = self.site.apply_shortcodes(output, filename=source_path, with_dependencies=True, extra_context={'post': post})
return output, shortcode_deps

def compile(self, source, dest, is_two_file=True, post=None, lang=None):
"""Compile the source file into HTML and save as dest."""
if markdown is None:
if Markdown is None:
req_missing(['markdown'], 'build this site (compile Markdown)')
makedirs(os.path.dirname(dest))
with io.open(dest, "w+", encoding="utf8") as out_file:
Expand Down

0 comments on commit 91e5581

Please sign in to comment.