|
| 1 | +#include "llvm/Support/Mustache.h" |
| 2 | +#include "benchmark/benchmark.h" |
| 3 | +#include "llvm/Support/JSON.h" |
| 4 | +#include "llvm/Support/raw_ostream.h" |
| 5 | +#include <string> |
| 6 | + |
| 7 | +// A large, raw string with many characters that require HTML escaping. |
| 8 | +static const std::string LongHtmlString = [] { |
| 9 | + std::string S; |
| 10 | + S.reserve(500000); |
| 11 | + for (int i = 0; i < 50000; ++i) { |
| 12 | + S += "<script>alert('xss');</script>"; |
| 13 | + } |
| 14 | + return S; |
| 15 | +}(); |
| 16 | + |
| 17 | +// A deep AND wide JSON object for testing traversal. |
| 18 | +static const llvm::json::Value DeepJsonData = [] { |
| 19 | + llvm::json::Value Root = llvm::json::Object(); |
| 20 | + llvm::json::Object *Current = Root.getAsObject(); |
| 21 | + for (int i = 0; i < 50; ++i) { // 50 levels deep |
| 22 | + for (int j = 0; j < 100; ++j) { |
| 23 | + (*Current)["sibling_" + std::to_string(j)] = llvm::json::Value("noise"); |
| 24 | + } |
| 25 | + std::string Key = "level_" + std::to_string(i); |
| 26 | + (*Current)[Key] = llvm::json::Object(); |
| 27 | + Current = (*Current)[Key].getAsObject(); |
| 28 | + } |
| 29 | + (*Current)["final_value"] = llvm::json::Value("Success!"); |
| 30 | + |
| 31 | + llvm::json::Array Arr; |
| 32 | + for (int i = 0; i < 5000; ++i) { // 5,000 iterations |
| 33 | + Arr.push_back(llvm::json::Value(i)); |
| 34 | + } |
| 35 | + |
| 36 | + llvm::json::Object NewRoot; |
| 37 | + NewRoot["deep_data"] = std::move(Root); |
| 38 | + NewRoot["loop_array"] = std::move(Arr); |
| 39 | + return llvm::json::Value(std::move(NewRoot)); |
| 40 | +}(); |
| 41 | + |
| 42 | +// A huge array for testing iteration performance. |
| 43 | +static const llvm::json::Value HugeArrayData = [] { |
| 44 | + llvm::json::Array Arr; |
| 45 | + for (int i = 0; i < 100000; ++i) { // 100,000 array items |
| 46 | + Arr.push_back(llvm::json::Object( |
| 47 | + {{"id", llvm::json::Value(static_cast<long long>(i))}, |
| 48 | + {"is_even", llvm::json::Value(i % 2 == 0)}, |
| 49 | + {"data", llvm::json::Value("Item data for " + std::to_string(i))}})); |
| 50 | + } |
| 51 | + return llvm::json::Object({{"items", std::move(Arr)}}); |
| 52 | +}(); |
| 53 | + |
| 54 | +// The main template that includes a partial within a loop. |
| 55 | +static const std::string ComplexPartialTemplate = |
| 56 | + "Header\n" |
| 57 | + "{{#items}}{{> item_partial}}{{/items}}\n" |
| 58 | + "Footer"; |
| 59 | + |
| 60 | +// The partial template is now more complex, rendering multiple fields and a |
| 61 | +// conditional section. |
| 62 | +static const std::string ItemPartialTemplate = |
| 63 | + "<div class=\"item\" id=\"{{id}}\">\n" |
| 64 | + " <p>{{data}}</p>\n" |
| 65 | + " {{#is_even}}<span>(Even)</span>{{/is_even}}\n" |
| 66 | + "</div>\n"; |
| 67 | + |
| 68 | +// A single large string to stress the output buffer. |
| 69 | +static const llvm::json::Value LargeOutputData = llvm::json::Object({ |
| 70 | + {"long_string", |
| 71 | + llvm::json::Value(std::string(1024 * 1024, 'A'))} // 1MB string |
| 72 | +}); |
| 73 | + |
| 74 | +// --- Static Data (Templates) --- |
| 75 | + |
| 76 | +static const std::string BulkEscapingTemplate = "{{content}}"; |
| 77 | +static const std::string BulkUnescapedTemplate = "{{{content}}}"; |
| 78 | +static const std::string BulkUnescapedAmpersandTemplate = "{{& content}}"; |
| 79 | + |
| 80 | +static const std::string DeepTraversalTemplate = [] { |
| 81 | + std::string LongKey = |
| 82 | + "deep_data.level_0.level_1.level_2.level_3.level_4.level_5." |
| 83 | + "level_6.level_7.level_8.level_9." |
| 84 | + "level_10.level_11.level_12.level_13.level_14.level_" |
| 85 | + "15.level_16.level_17.level_18.level_19." |
| 86 | + "level_20.level_21.level_22.level_23.level_24.level_" |
| 87 | + "25.level_26.level_27.level_28.level_29." |
| 88 | + "level_30.level_31.level_32.level_33.level_34.level_" |
| 89 | + "35.level_36.level_37.level_38.level_39." |
| 90 | + "level_40.level_41.level_42.level_43.level_44.level_" |
| 91 | + "45.level_46.level_47.level_48.level_49.final_value"; |
| 92 | + return "{{#loop_array}}{{" + LongKey + "}}{{/loop_array}}"; |
| 93 | +}(); |
| 94 | + |
| 95 | +static const std::string DeeplyNestedRenderingTemplate = [] { |
| 96 | + std::string NestedTemplate = "{{#deep_data}}"; |
| 97 | + for (int i = 0; i < 50; ++i) { |
| 98 | + NestedTemplate += "{{#level_" + std::to_string(i) + "}}"; |
| 99 | + } |
| 100 | + NestedTemplate += "{{final_value}}"; |
| 101 | + for (int i = 49; i >= 0; --i) { |
| 102 | + NestedTemplate += "{{/level_" + std::to_string(i) + "}}"; |
| 103 | + } |
| 104 | + NestedTemplate += "{{/deep_data}}"; |
| 105 | + return NestedTemplate; |
| 106 | +}(); |
| 107 | + |
| 108 | +static const std::string HugeArrayIterationTemplate = |
| 109 | + "{{#items}}ID: {{id}}.{{/items}}"; |
| 110 | + |
| 111 | +static const std::string ComplexTemplateParsingTemplate = [] { |
| 112 | + std::string LargeTemplate; |
| 113 | + LargeTemplate.reserve(100000); |
| 114 | + for (int i = 0; i < 1000; ++i) { |
| 115 | + LargeTemplate += "{{var_" + std::to_string(i) + |
| 116 | + "}}" |
| 117 | + "{{#section_" + |
| 118 | + std::to_string(i) + "}}Content{{/section_" + |
| 119 | + std::to_string(i) + |
| 120 | + "}}" |
| 121 | + "{{!comment_" + |
| 122 | + std::to_string(i) + |
| 123 | + "}}" |
| 124 | + "{{=<% %>=}}" |
| 125 | + "<%var_tag_changed_to_percent_sign_" + |
| 126 | + std::to_string(i) + |
| 127 | + "%>" |
| 128 | + "<%={{ }}=%>" |
| 129 | + "{{^inverted_" + |
| 130 | + std::to_string(i) + "}}Not Present{{/inverted_" + |
| 131 | + std::to_string(i) + "}}"; |
| 132 | + } |
| 133 | + return LargeTemplate; |
| 134 | +}(); |
| 135 | + |
| 136 | +static const std::string SmallTemplateParsingTemplate = |
| 137 | + "{{level_0.sibling_99}}\n" |
| 138 | + "{{level_0.level_1.level_2.level_3.level_4.level_5.sibling_50}}\n" |
| 139 | + "{{level_0.level_1.level_2.level_3.level_4.level_5." |
| 140 | + "level_6.level_7.level_8.level_9." |
| 141 | + "level_10.level_11.level_12.level_13.level_14.level_" |
| 142 | + "15.level_16.level_17.level_18.level_19." |
| 143 | + "level_20.level_21.level_22.level_23.level_24.level_" |
| 144 | + "25.level_26.level_27.level_28.level_29." |
| 145 | + "level_30.level_31.level_32.level_33.level_34.level_" |
| 146 | + "35.level_36.level_37.level_38.level_39." |
| 147 | + "level_40.level_41.level_42.level_43.level_44.level_" |
| 148 | + "45.level_46.level_47.level_48.level_49.final_value}}\n"; |
| 149 | + |
| 150 | +static const std::string LargeOutputStringTemplate = "{{long_string}}"; |
| 151 | + |
| 152 | +// Tests the performance of rendering a large string with various escaping |
| 153 | +// syntaxes. |
| 154 | +static void BM_Mustache_StringRendering(benchmark::State &state, |
| 155 | + const std::string &TplStr) { |
| 156 | + llvm::mustache::Template Tpl(TplStr); |
| 157 | + llvm::json::Value Data = |
| 158 | + llvm::json::Object({{"content", llvm::json::Value(LongHtmlString)}}); |
| 159 | + for (auto _ : state) { |
| 160 | + std::string Result; |
| 161 | + llvm::raw_string_ostream OS(Result); |
| 162 | + Tpl.render(Data, OS); |
| 163 | + benchmark::DoNotOptimize(Result); |
| 164 | + } |
| 165 | +} |
| 166 | +BENCHMARK_CAPTURE(BM_Mustache_StringRendering, Escaped, BulkEscapingTemplate); |
| 167 | +BENCHMARK_CAPTURE(BM_Mustache_StringRendering, Unescaped_Triple, |
| 168 | + BulkUnescapedTemplate); |
| 169 | +BENCHMARK_CAPTURE(BM_Mustache_StringRendering, Unescaped_Ampersand, |
| 170 | + BulkUnescapedAmpersandTemplate); |
| 171 | + |
| 172 | +// Tests the "hot render" cost of repeatedly traversing a deep and wide |
| 173 | +// JSON object. |
| 174 | +static void BM_Mustache_DeepTraversal(benchmark::State &state) { |
| 175 | + llvm::mustache::Template Tpl(DeepTraversalTemplate); |
| 176 | + for (auto _ : state) { |
| 177 | + std::string Result; |
| 178 | + llvm::raw_string_ostream OS(Result); |
| 179 | + Tpl.render(DeepJsonData, OS); |
| 180 | + benchmark::DoNotOptimize(Result); |
| 181 | + } |
| 182 | +} |
| 183 | +BENCHMARK(BM_Mustache_DeepTraversal); |
| 184 | + |
| 185 | +// Tests the "hot render" cost of pushing and popping a deep context stack. |
| 186 | +static void BM_Mustache_DeeplyNestedRendering(benchmark::State &state) { |
| 187 | + llvm::mustache::Template Tpl(DeeplyNestedRenderingTemplate); |
| 188 | + for (auto _ : state) { |
| 189 | + std::string Result; |
| 190 | + llvm::raw_string_ostream OS(Result); |
| 191 | + Tpl.render(DeepJsonData, OS); |
| 192 | + benchmark::DoNotOptimize(Result); |
| 193 | + } |
| 194 | +} |
| 195 | +BENCHMARK(BM_Mustache_DeeplyNestedRendering); |
| 196 | + |
| 197 | +// Tests the performance of the loop logic when iterating over a huge number of |
| 198 | +// items. |
| 199 | +static void BM_Mustache_HugeArrayIteration(benchmark::State &state) { |
| 200 | + llvm::mustache::Template Tpl(HugeArrayIterationTemplate); |
| 201 | + for (auto _ : state) { |
| 202 | + std::string Result; |
| 203 | + llvm::raw_string_ostream OS(Result); |
| 204 | + Tpl.render(HugeArrayData, OS); |
| 205 | + benchmark::DoNotOptimize(Result); |
| 206 | + } |
| 207 | +} |
| 208 | +BENCHMARK(BM_Mustache_HugeArrayIteration); |
| 209 | + |
| 210 | +// Tests the performance of the parser on a large, "wide" template. |
| 211 | +static void BM_Mustache_ComplexTemplateParsing(benchmark::State &state) { |
| 212 | + for (auto _ : state) { |
| 213 | + llvm::mustache::Template Tpl(ComplexTemplateParsingTemplate); |
| 214 | + benchmark::DoNotOptimize(Tpl); |
| 215 | + } |
| 216 | +} |
| 217 | +BENCHMARK(BM_Mustache_ComplexTemplateParsing); |
| 218 | + |
| 219 | +// Tests the performance of the parser on a small, "deep" template. |
| 220 | +static void BM_Mustache_SmallTemplateParsing(benchmark::State &state) { |
| 221 | + for (auto _ : state) { |
| 222 | + llvm::mustache::Template Tpl(SmallTemplateParsingTemplate); |
| 223 | + benchmark::DoNotOptimize(Tpl); |
| 224 | + } |
| 225 | +} |
| 226 | +BENCHMARK(BM_Mustache_SmallTemplateParsing); |
| 227 | + |
| 228 | +// Tests the performance of rendering a template that includes a partial. |
| 229 | +static void BM_Mustache_PartialsRendering(benchmark::State &state) { |
| 230 | + llvm::mustache::Template Tpl(ComplexPartialTemplate); |
| 231 | + Tpl.registerPartial("item_partial", ItemPartialTemplate); |
| 232 | + llvm::json::Value Data = HugeArrayData; |
| 233 | + |
| 234 | + for (auto _ : state) { |
| 235 | + std::string Result; |
| 236 | + llvm::raw_string_ostream OS(Result); |
| 237 | + Tpl.render(Data, OS); |
| 238 | + benchmark::DoNotOptimize(Result); |
| 239 | + } |
| 240 | +} |
| 241 | +BENCHMARK(BM_Mustache_PartialsRendering); |
| 242 | + |
| 243 | +// Tests the performance of the underlying buffer management when generating a |
| 244 | +// very large output. |
| 245 | +static void BM_Mustache_LargeOutputString(benchmark::State &state) { |
| 246 | + llvm::mustache::Template Tpl(LargeOutputStringTemplate); |
| 247 | + for (auto _ : state) { |
| 248 | + std::string Result; |
| 249 | + llvm::raw_string_ostream OS(Result); |
| 250 | + Tpl.render(LargeOutputData, OS); |
| 251 | + benchmark::DoNotOptimize(Result); |
| 252 | + } |
| 253 | +} |
| 254 | +BENCHMARK(BM_Mustache_LargeOutputString); |
| 255 | + |
| 256 | +BENCHMARK_MAIN(); |
0 commit comments