diff --git a/common/font/font.cpp b/common/font/font.cpp
index 45ed4b6a6c..d5ccaf9da6 100644
--- a/common/font/font.cpp
+++ b/common/font/font.cpp
@@ -24,6 +24,10 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
  */
 
+#include <list>
+#include <mutex>
+#include <unordered_map>
+
 #include <wx/font.h>
 #include <string_utils.h>
 #include <gal/graphics_abstraction_layer.h>
@@ -47,6 +51,75 @@ FONT* FONT::s_defaultFont = nullptr;
 
 std::map< std::tuple<wxString, bool, bool>, FONT*> FONT::s_fontMap;
 
+class MARKUP_CACHE
+{
+public:
+    struct ENTRY
+    {
+        std::string source;
+        std::unique_ptr<MARKUP::NODE> root;
+    };
+
+    typedef std::pair<wxString, ENTRY> CACHE_ENTRY;
+
+    MARKUP_CACHE( size_t aMaxSize ) :
+            m_maxSize( aMaxSize )
+    {
+    }
+
+    ENTRY& Put( const CACHE_ENTRY::first_type& aQuery, ENTRY&& aResult )
+    {
+        auto it = m_cache.find( aQuery );
+
+        m_cacheMru.emplace_front( CACHE_ENTRY( aQuery, std::move( aResult ) ) );
+
+        if( it != m_cache.end() )
+        {
+            m_cacheMru.erase( it->second );
+            m_cache.erase( it );
+        }
+
+        m_cache[aQuery] = m_cacheMru.begin();
+
+        if( m_cache.size() > m_maxSize )
+        {
+            auto last = m_cacheMru.end();
+            last--;
+            m_cache.erase( last->first );
+            m_cacheMru.pop_back();
+        }
+
+        return m_cacheMru.begin()->second;
+    }
+
+    ENTRY* Get( const CACHE_ENTRY::first_type& aQuery )
+    {
+        auto it = m_cache.find( aQuery );
+
+        if( it == m_cache.end() )
+            return nullptr;
+
+        m_cacheMru.splice( m_cacheMru.begin(), m_cacheMru, it->second );
+
+        return &m_cacheMru.begin()->second;
+    }
+
+    void Clear()
+    {
+        m_cacheMru.clear();
+        m_cache.clear();
+    }
+
+private:
+    size_t                                                         m_maxSize;
+    std::list<CACHE_ENTRY>                                         m_cacheMru;
+    std::unordered_map<wxString, std::list<CACHE_ENTRY>::iterator> m_cache;
+};
+
+
+static MARKUP_CACHE s_markupCache( 1024 );
+static std::mutex s_markupCacheMutex;
+
 
 FONT::FONT()
 {
@@ -106,7 +179,7 @@ void FONT::getLinePositions( const wxString& aText, const VECTOR2I& aPosition,
     {
         VECTOR2I pos( aPosition.x, aPosition.y + i * interline );
         VECTOR2I end = boundingBoxSingleLine( nullptr, aTextLines[i], pos, aAttrs.m_Size,
-                                              aAttrs.m_Italic );
+                                                aAttrs.m_Italic );
         VECTOR2I bBox( end - pos );
 
         aExtents.push_back( bBox );
@@ -186,7 +259,7 @@ void FONT::Draw( KIGFX::GAL* aGal, const wxString& aText, const VECTOR2I& aPosit
  * @return position of cursor for drawing next substring
  */
 VECTOR2I drawMarkup( BOX2I* aBoundingBox, std::vector<std::unique_ptr<GLYPH>>* aGlyphs,
-                     const std::unique_ptr<MARKUP::NODE>& aNode, const VECTOR2I& aPosition,
+                     const MARKUP::NODE* aNode, const VECTOR2I& aPosition,
                      const KIFONT::FONT* aFont, const VECTOR2I& aSize, const EDA_ANGLE& aAngle,
                      bool aMirror, const VECTOR2I& aOrigin, TEXT_STYLE_FLAGS aTextStyle )
 {
@@ -221,8 +294,8 @@ VECTOR2I drawMarkup( BOX2I* aBoundingBox, std::vector<std::unique_ptr<GLYPH>>* a
 
         for( const std::unique_ptr<MARKUP::NODE>& child : aNode->children )
         {
-            nextPosition = drawMarkup( aBoundingBox, aGlyphs, child, nextPosition, aFont, aSize,
-                                       aAngle, aMirror, aOrigin, textStyle );
+            nextPosition = drawMarkup( aBoundingBox, aGlyphs, child.get(), nextPosition, aFont,
+                                       aSize, aAngle, aMirror, aOrigin, textStyle );
         }
     }
 
@@ -235,11 +308,24 @@ VECTOR2I FONT::drawMarkup( BOX2I* aBoundingBox, std::vector<std::unique_ptr<GLYP
                            const EDA_ANGLE& aAngle, bool aMirror, const VECTOR2I& aOrigin,
                            TEXT_STYLE_FLAGS aTextStyle ) const
 {
-    MARKUP::MARKUP_PARSER         markupParser( TO_UTF8( aText ) );
-    std::unique_ptr<MARKUP::NODE> root = markupParser.Parse();
+    std::lock_guard<std::mutex> lock( s_markupCacheMutex );
 
-    return ::drawMarkup( aBoundingBox, aGlyphs, root, aPosition, this, aSize, aAngle, aMirror,
-                         aOrigin, aTextStyle );
+    MARKUP_CACHE::ENTRY* markup = s_markupCache.Get( aText );
+
+    if( !markup || !markup->root )
+    {
+        MARKUP_CACHE::ENTRY& cached = s_markupCache.Put( aText, {} );
+
+        cached.source = TO_UTF8( aText );
+        MARKUP::MARKUP_PARSER markupParser( &cached.source );
+        cached.root = markupParser.Parse();
+        markup = &cached;
+    }
+
+    wxASSERT( markup && markup->root );
+
+    return ::drawMarkup( aBoundingBox, aGlyphs, markup->root.get(), aPosition, this, aSize, aAngle,
+                         aMirror, aOrigin, aTextStyle );
 }
 
 
diff --git a/common/markup_parser.cpp b/common/markup_parser.cpp
index 770f8d489a..e94f522177 100644
--- a/common/markup_parser.cpp
+++ b/common/markup_parser.cpp
@@ -30,7 +30,13 @@ std::unique_ptr<NODE> MARKUP_PARSER::Parse()
 {
     try
     {
-        auto root = parse_tree::parse<MARKUP::grammar, MARKUP::NODE, MARKUP::selector>( in );
+        std::unique_ptr<NODE> root;
+
+        if( mem_in )
+            root = parse_tree::parse<MARKUP::grammar, MARKUP::NODE, MARKUP::selector>( *mem_in );
+        else
+            root = parse_tree::parse<MARKUP::grammar, MARKUP::NODE, MARKUP::selector>( *in );
+
         return root;
     }
     catch ( tao::pegtl::parse_error& )
diff --git a/common/template_fieldnames.cpp b/common/template_fieldnames.cpp
index 022a7bb53a..044218dc80 100644
--- a/common/template_fieldnames.cpp
+++ b/common/template_fieldnames.cpp
@@ -36,16 +36,21 @@ using namespace TFIELD_T;
 #define FOOTPRINT_CANONICAL "Footprint"
 #define DATASHEET_CANONICAL "Datasheet"
 
+static wxString s_CanonicalReference( REFERENCE_CANONICAL );
+static wxString s_CanonicalValue( VALUE_CANONICAL );
+static wxString s_CanonicalFootprint( FOOTPRINT_CANONICAL );
+static wxString s_CanonicalDatasheet( DATASHEET_CANONICAL );
+
 const wxString TEMPLATE_FIELDNAME::GetDefaultFieldName( int aFieldNdx, bool aTranslateForHI )
 {
     if( !aTranslateForHI )
     {
         switch( aFieldNdx )
         {
-        case  REFERENCE_FIELD: return REFERENCE_CANONICAL;   // The symbol reference, R1, C1, etc.
-        case  VALUE_FIELD:     return VALUE_CANONICAL;       // The symbol value
-        case  FOOTPRINT_FIELD: return FOOTPRINT_CANONICAL;   // The footprint for use with Pcbnew
-        case  DATASHEET_FIELD: return DATASHEET_CANONICAL;   // Link to a datasheet for symbol
+        case  REFERENCE_FIELD: return s_CanonicalReference;   // The symbol reference, R1, C1, etc.
+        case  VALUE_FIELD:     return s_CanonicalValue;       // The symbol value
+        case  FOOTPRINT_FIELD: return s_CanonicalFootprint;   // The footprint for use with Pcbnew
+        case  DATASHEET_FIELD: return s_CanonicalDatasheet;   // Link to a datasheet for symbol
         default:               return wxString::Format( wxT( "Field%d" ), aFieldNdx );
         }
     }
diff --git a/common/transform.cpp b/common/transform.cpp
index d805c68db4..0499b4ee04 100644
--- a/common/transform.cpp
+++ b/common/transform.cpp
@@ -22,6 +22,7 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
  */
 
+#include <hash.h>
 #include <macros.h>
 #include <trigo.h>
 #include <transform.h>
@@ -124,3 +125,9 @@ bool TRANSFORM::MapAngles( EDA_ANGLE* aAngle1, EDA_ANGLE* aAngle2 ) const
 }
 
 
+size_t std::hash<TRANSFORM>::operator()( const TRANSFORM& s ) const
+{
+    size_t seed = std::hash<int>{}( s.x1 );
+    hash_combine( seed, s.y1, s.x2, s.y2 );
+    return seed;
+}
diff --git a/eeschema/sch_symbol.cpp b/eeschema/sch_symbol.cpp
index 571338c81c..713eb1ce76 100644
--- a/eeschema/sch_symbol.cpp
+++ b/eeschema/sch_symbol.cpp
@@ -44,6 +44,9 @@
 #include "plotters/plotter.h"
 
 
+std::unordered_map<TRANSFORM, int> SCH_SYMBOL::s_transformToOrientationCache;
+
+
 /**
  * Convert a wxString to UTF8 and replace any control characters with a ~,
  * where a control character is one of the first ASCII values up to ' ' 32d.
@@ -1535,6 +1538,13 @@ void SCH_SYMBOL::SetOrientation( int aOrientation )
 
 int SCH_SYMBOL::GetOrientation() const
 {
+    /*
+     * This is slow, but also a bizarre algorithm.  I don't feel like unteasing the algorithm right
+     * now, so let's just cache it for the moment.
+     */
+    if( s_transformToOrientationCache.count( m_transform ) )
+        return s_transformToOrientationCache.at( m_transform );
+
     int rotate_values[] =
     {
         SYM_ORIENT_0,
@@ -1560,7 +1570,10 @@ int SCH_SYMBOL::GetOrientation() const
         temp.SetOrientation( type_rotate );
 
         if( transform == temp.GetTransform() )
+        {
+            s_transformToOrientationCache[m_transform] = type_rotate;
             return type_rotate;
+        }
     }
 
     // Error: orientation not found in list (should not happen)
diff --git a/eeschema/sch_symbol.h b/eeschema/sch_symbol.h
index 9c5ee0b314..f36a06bdc2 100644
--- a/eeschema/sch_symbol.h
+++ b/eeschema/sch_symbol.h
@@ -798,6 +798,9 @@ private:
     // Defines the hierarchical path and reference of the symbol.  This allows support
     // for multiple references to a single sub-sheet.
     std::vector<SCH_SYMBOL_INSTANCE> m_instanceReferences;
+
+    /// @see SCH_SYMBOL::GetOrientation
+    static std::unordered_map<TRANSFORM, int> s_transformToOrientationCache;
 };
 
 #endif /* __SYMBOL_H__ */
diff --git a/include/markup_parser.h b/include/markup_parser.h
index 99b97858b4..7427e4aff8 100644
--- a/include/markup_parser.h
+++ b/include/markup_parser.h
@@ -97,13 +97,20 @@ class MARKUP_PARSER
 {
 public:
     MARKUP_PARSER( const std::string& source ) :
-            in( source, "from_input" )
+            in( std::make_unique<string_input<>>( source, "from_input" ) ),
+            mem_in()
+    {}
+
+    MARKUP_PARSER( const std::string* source ) :
+            in(),
+            mem_in( std::make_unique<memory_input<>>( *source, "from_input" ) )
     {}
 
     std::unique_ptr<NODE> Parse();
 
 private:
-    string_input<> in;
+    std::unique_ptr<string_input<>> in;
+    std::unique_ptr<memory_input<>> mem_in;
 };
 
 } // namespace MARKUP
diff --git a/include/transform.h b/include/transform.h
index 726302bfe8..61a6cea025 100644
--- a/include/transform.h
+++ b/include/transform.h
@@ -98,5 +98,12 @@ public:
    bool MapAngles( EDA_ANGLE* aAngle1, EDA_ANGLE* aAngle2 ) const;
 };
 
+namespace std
+{
+    template <> struct hash<TRANSFORM>
+    {
+        size_t operator() ( const TRANSFORM& k ) const;
+    };
+}
 
 #endif    // _TRANSFORM_H_