[llvm-branch-commits] [llvm] [llvm][mustache] Avoid excessive hash lookups in EscapeStringStream (PR #160166)

Paul Kirth via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Mon Sep 22 11:20:50 PDT 2025


https://github.com/ilovepi created https://github.com/llvm/llvm-project/pull/160166

The naive char-by-char lookup performed OK, but we can skip ahead to the
next match, avoiding all the extra hash lookups in the key map. Likely
there is a faster method than this, but its already a 42% win in the
BM_Mustache_StringRendering/Escaped benchmark, and an order of magnitude
improvement for BM_Mustache_LargeOutputString.

 Benchmark                  Before (ns)   After (ns)  Speedup
 -------------------------  -----------  -----------  -------
 StringRendering/Escaped     29,440,922   16,583,603     ~44%
 LargeOutputString           15,139,251      929,891     ~94%
 HugeArrayIteration         102,148,245   95,943,960      ~6%
 PartialsRendering          308,330,014  303,556,563    ~1.6%

Unreported benchmarks, like those for parsing, had no significant change.

>From 29e37be8957bb486722fe52ba404501031b1691e Mon Sep 17 00:00:00 2001
From: Paul Kirth <pk1574 at gmail.com>
Date: Mon, 22 Sep 2025 16:26:04 +0000
Subject: [PATCH] [llvm][mustache] Avoid excessive hash lookups in
 EscapeStringStream

The naive char-by-char lookup performed OK, but we can skip ahead to the
next match, avoiding all the extra hash lookups in the key map. Likely
there is a faster method than this, but its already a 42% win in the
BM_Mustache_StringRendering/Escaped benchmark, and an order of magnitude
improvement for BM_Mustache_LargeOutputString.

 Benchmark                  Before (ns)   After (ns)  Speedup
 -------------------------  -----------  -----------  -------
 StringRendering/Escaped     29,440,922   16,583,603     ~44%
 LargeOutputString           15,139,251      929,891     ~94%
 HugeArrayIteration         102,148,245   95,943,960      ~6%
 PartialsRendering          308,330,014  303,556,563    ~1.6%

Unreported benchmarks, like those for parsing, had no significant change.
---
 llvm/lib/Support/Mustache.cpp | 30 ++++++++++++++++++++++--------
 1 file changed, 22 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp
index c7cebe6b64fae..911fd5ee7fa01 100644
--- a/llvm/lib/Support/Mustache.cpp
+++ b/llvm/lib/Support/Mustache.cpp
@@ -428,19 +428,32 @@ class EscapeStringStream : public raw_ostream {
 public:
   explicit EscapeStringStream(llvm::raw_ostream &WrappedStream,
                               EscapeMap &Escape)
-      : Escape(Escape), WrappedStream(WrappedStream) {
+      : Escape(Escape), EscapeChars(Escape.keys().begin(), Escape.keys().end()),
+        WrappedStream(WrappedStream) {
     SetUnbuffered();
   }
 
 protected:
   void write_impl(const char *Ptr, size_t Size) override {
-    llvm::StringRef Data(Ptr, Size);
-    for (char C : Data) {
-      auto It = Escape.find(C);
-      if (It != Escape.end())
-        WrappedStream << It->getSecond();
-      else
-        WrappedStream << C;
+    StringRef Data(Ptr, Size);
+    size_t Start = 0;
+    while (Start < Size) {
+      // Find the next character that needs to be escaped.
+      size_t Next = Data.find_first_of(EscapeChars.str(), Start);
+
+      // If no escapable characters are found, write the rest of the string.
+      if (Next == StringRef::npos) {
+        WrappedStream << Data.substr(Start);
+        return;
+      }
+
+      // Write the chunk of text before the escapable character.
+      if (Next > Start)
+        WrappedStream << Data.substr(Start, Next - Start);
+
+      // Look up and write the escaped version of the character.
+      WrappedStream << Escape[Data[Next]];
+      Start = Next + 1;
     }
   }
 
@@ -448,6 +461,7 @@ class EscapeStringStream : public raw_ostream {
 
 private:
   EscapeMap &Escape;
+  SmallString<8> EscapeChars;
   llvm::raw_ostream &WrappedStream;
 };
 



More information about the llvm-branch-commits mailing list