Skip to content

Commit 3d782f1

Browse files
authored
optimise template load time (#241)
trying to fix #237 according to research https://github.com/HFTrader/regex-performance std::regex is extremely slow x times slower than boost one's ==> changing default regex engine to boost one's presering ability to build jinja2cpp with standard regex on the local PC speed up of using boost::regex instead of std::regex shows up to be 5-6x times 20s to 120s
1 parent 05014bf commit 3d782f1

File tree

5 files changed

+73
-18
lines changed

5 files changed

+73
-18
lines changed

CMakeLists.txt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ endif()
1515
set(JINJA2CPP_SANITIZERS address+undefined memory)
1616
set(JINJA2CPP_WITH_SANITIZERS none CACHE STRING "Build with sanitizer")
1717
set_property(CACHE JINJA2CPP_WITH_SANITIZERS PROPERTY STRINGS ${JINJA2CPP_SANITIZERS})
18+
set(JINJA2CPP_SUPPORTED_REGEX std boost)
19+
set(JINJA2CPP_USE_REGEX boost CACHE STRING "Use regex parser in lexer, boost works faster on most platforms")
20+
set_property(CACHE JINJA2CPP_USE_REGEX PROPERTY STRINGS ${JINJA2CPP_SUPPORTED_REGEX})
1821
set(JINJA2CPP_WITH_JSON_BINDINGS boost nlohmann rapid all none)
1922
set(JINJA2CPP_WITH_JSON_BINDINGS all CACHE STRING "Build with json support")
2023
set_property(CACHE JINJA2CPP_WITH_JSON_BINDINGS PROPERTY STRINGS ${JINJA2CPP_WITH_JSON_BINDINGS})
@@ -210,7 +213,15 @@ elseif (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
210213
target_compile_options(${LIB_TARGET_NAME} PRIVATE ${MSVC_CXX_FLAGS})
211214
endif ()
212215

213-
target_compile_definitions(${LIB_TARGET_NAME} PUBLIC -DBOOST_SYSTEM_NO_DEPRECATED -DBOOST_ERROR_CODE_HEADER_ONLY)
216+
if ("${JINJA2CPP_USE_REGEX}" STREQUAL "boost")
217+
set(_regex_define "-DJINJA2CPP_USE_REGEX_BOOST")
218+
endif()
219+
target_compile_definitions(${LIB_TARGET_NAME}
220+
PUBLIC
221+
-DBOOST_SYSTEM_NO_DEPRECATED
222+
-DBOOST_ERROR_CODE_HEADER_ONLY
223+
${_regex_define}
224+
)
214225

215226
if (JINJA2CPP_BUILD_SHARED)
216227
target_compile_definitions(${LIB_TARGET_NAME} PRIVATE -DJINJA2CPP_BUILD_AS_SHARED PUBLIC -DJINJA2CPP_LINK_AS_SHARED)

src/template_parser.h

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,28 @@
1717
#include <nonstd/expected.hpp>
1818

1919
#include <list>
20-
#include <regex>
2120
#include <sstream>
2221
#include <string>
2322
#include <vector>
2423

24+
#ifdef JINJA2CPP_USE_REGEX_BOOST
25+
#include <boost/regex.hpp>
26+
template <typename CharType>
27+
using BasicRegex = boost::basic_regex<CharType>;
28+
using Regex = boost::regex;
29+
using WideRegex = boost::wregex;
30+
template <typename CharIterator>
31+
using RegexIterator = boost::regex_iterator<CharIterator>;
32+
#else
33+
#include <regex>
34+
template <typename CharType>
35+
using BasicRegex = std::basic_regex<CharType>;
36+
using Regex = std::regex;
37+
using WideRegex = std::wregex;
38+
template <typename CharIterator>
39+
using RegexIterator = std::regex_iterator<CharIterator>;
40+
#endif
41+
2542
namespace jinja2
2643
{
2744
template<typename CharT>
@@ -58,9 +75,9 @@ MultiStringLiteral ParserTraitsBase<T>::s_regexp = UNIVERSAL_STR(
5875
template<>
5976
struct ParserTraits<char> : public ParserTraitsBase<>
6077
{
61-
static std::regex GetRoughTokenizer()
62-
{ return std::regex(s_regexp.GetValueStr<char>()); }
63-
static std::regex GetKeywords()
78+
static Regex GetRoughTokenizer()
79+
{ return Regex(s_regexp.GetValueStr<char>()); }
80+
static Regex GetKeywords()
6481
{
6582
std::string pattern;
6683
std::string prefix("(^");
@@ -76,7 +93,7 @@ struct ParserTraits<char> : public ParserTraitsBase<>
7693

7794
pattern += prefix + info.name.charValue + postfix;
7895
}
79-
return std::regex(pattern);
96+
return Regex(pattern);
8097
}
8198
static std::string GetAsString(const std::string& str, CharRange range) { return str.substr(range.startOffset, range.size()); }
8299
static InternalValue RangeToNum(const std::string& str, CharRange range, Token::Type hint)
@@ -109,9 +126,9 @@ struct ParserTraits<char> : public ParserTraitsBase<>
109126
template<>
110127
struct ParserTraits<wchar_t> : public ParserTraitsBase<>
111128
{
112-
static std::wregex GetRoughTokenizer()
113-
{ return std::wregex(s_regexp.GetValueStr<wchar_t>()); }
114-
static std::wregex GetKeywords()
129+
static WideRegex GetRoughTokenizer()
130+
{ return WideRegex(s_regexp.GetValueStr<wchar_t>()); }
131+
static WideRegex GetKeywords()
115132
{
116133
std::wstring pattern;
117134
std::wstring prefix(L"(^");
@@ -127,7 +144,7 @@ struct ParserTraits<wchar_t> : public ParserTraitsBase<>
127144

128145
pattern += prefix + info.name.wcharValue + postfix;
129146
}
130-
return std::wregex(pattern);
147+
return WideRegex(pattern);
131148
}
132149
static std::string GetAsString(const std::wstring& str, CharRange range)
133150
{
@@ -248,7 +265,7 @@ class TemplateParser : public LexerHelper
248265
public:
249266
using string_t = std::basic_string<CharT>;
250267
using traits_t = ParserTraits<CharT>;
251-
using sregex_iterator = std::regex_iterator<typename string_t::const_iterator>;
268+
using sregex_iterator = RegexIterator<typename string_t::const_iterator>;
252269
using ErrorInfo = ErrorInfoTpl<CharT>;
253270
using ParseResult = nonstd::expected<RendererPtr, std::vector<ErrorInfo>>;
254271

@@ -437,7 +454,7 @@ class TemplateParser : public LexerHelper
437454
FinishCurrentLine(match.position() + 2);
438455
return MakeParseError(ErrorCode::UnexpectedCommentEnd, MakeToken(Token::CommentEnd, { matchStart, matchStart + 2 }));
439456
}
440-
457+
441458
m_currentBlockInfo.range.startOffset = FinishCurrentBlock(matchStart, TextBlockType::RawText);
442459
break;
443460
case RM_ExprBegin:
@@ -925,8 +942,8 @@ class TemplateParser : public LexerHelper
925942
std::string m_templateName;
926943
const Settings& m_settings;
927944
TemplateEnv* m_env = nullptr;
928-
std::basic_regex<CharT> m_roughTokenizer;
929-
std::basic_regex<CharT> m_keywords;
945+
BasicRegex<CharT> m_roughTokenizer;
946+
BasicRegex<CharT> m_keywords;
930947
std::vector<LineInfo> m_lines;
931948
std::vector<TextBlockInfo> m_textBlocks;
932949
LineInfo m_currentLineInfo = {};

test/perf_test.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,26 @@ TEST(PerfTests, ForLoopIfText)
181181
std::cout << result << std::endl;
182182
}
183183

184+
TEST(PerfTests, LoadTemplate)
185+
{
186+
std::string source = "{{ title }}";
187+
jinja2::Template tpl;
188+
189+
const int N = 100000;
190+
191+
for (int i = 0; i < N; i++)
192+
{
193+
tpl.Load(source);
194+
}
195+
196+
ValuesMap data;
197+
data["title"] = "My title";
198+
data["t"] = "My List";
199+
std::string result = tpl.RenderAsString(data).value();;
200+
201+
std::cout << result << std::endl;
202+
}
203+
184204
TEST(PerfTests, DISABLED_TestMatsuhiko)
185205
{
186206
std::string source = R"(

thirdparty/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ endif()
6868

6969
if (NOT DEFINED JINJA2_PRIVATE_LIBS_INT)
7070
set(JINJA2CPP_PRIVATE_LIBS ${JINJA2CPP_PRIVATE_LIBS} Boost::variant
71-
Boost::filesystem Boost::algorithm Boost::lexical_cast Boost::json fmt RapidJson)
71+
Boost::filesystem Boost::algorithm Boost::lexical_cast Boost::json Boost::regex fmt RapidJson)
7272
else ()
7373
set (JINJA2CPP_PRIVATE_LIBS ${JINJA2_PRIVATE_LIBS_INT})
7474
endif ()

thirdparty/external_boost_deps.cmake

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,30 +24,37 @@ find_package(boost_filesystem ${FIND_BOOST_PACKAGE_QUIET})
2424
find_package(boost_json ${FIND_BOOST_PACKAGE_QUIET})
2525
find_package(boost_optional ${FIND_BOOST_PACKAGE_QUIET})
2626
find_package(boost_variant ${FIND_BOOST_PACKAGE_QUIET})
27+
find_package(boost_regex ${FIND_BOOST_PACKAGE_QUIET})
2728

2829
if (boost_algorithm_FOUND AND
2930
boost_filesystem_FOUND AND
3031
boost_json_FOUND AND
3132
boost_optional_FOUND AND
32-
boost_variant_FOUND)
33+
boost_variant_FOUND AND boost_regex_FOUND)
3334
imported_target_alias(boost_algorithm ALIAS boost_algorithm::boost_algorithm)
3435
imported_target_alias(boost_filesystem ALIAS boost_filesystem::boost_filesystem)
3536
imported_target_alias(boost_json ALIAS boost_json::boost_json)
3637
imported_target_alias(boost_optional ALIAS boost_optional::boost_optional)
3738
imported_target_alias(boost_variant ALIAS boost_variant::boost_variant)
39+
imported_target_alias(boost_regex ALIAS boost_regex::boost_regex)
3840
else ()
39-
find_package(Boost COMPONENTS system filesystem json ${FIND_BOOST_PACKAGE_QUIET} REQUIRED)
41+
find_package(Boost COMPONENTS system filesystem json regex ${FIND_BOOST_PACKAGE_QUIET} REQUIRED)
4042

4143
if (Boost_FOUND)
4244
imported_target_alias(boost_algorithm ALIAS Boost::boost)
4345
imported_target_alias(boost_filesystem ALIAS Boost::filesystem)
4446
imported_target_alias(boost_json ALIAS Boost::json)
4547
imported_target_alias(boost_optional ALIAS Boost::boost)
4648
imported_target_alias(boost_variant ALIAS Boost::boost)
49+
imported_target_alias(boost_regex ALIAS Boost::regex)
4750
endif ()
4851
endif ()
4952

50-
install(TARGETS boost_algorithm boost_filesystem boost_json boost_optional boost_variant
53+
set(_additional_boost_install_targets)
54+
if ("${JINJA2CPP_USE_REGEX}" STREQUAL "boost")
55+
set(_additional_boost_install_targets "boost_regex")
56+
endif()
57+
install(TARGETS boost_algorithm boost_filesystem boost_json boost_optional boost_variant ${_additional_boost_install_targets}
5158
EXPORT InstallTargets
5259
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
5360
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}

0 commit comments

Comments
 (0)