Skip to content

Conversation

ilovepi
Copy link
Contributor

@ilovepi ilovepi commented Sep 16, 2025

The splitMustacheString function previously used a loop of
StringRef::split and StringRef::trim. This was inefficient as
it scanned each segment of the accessor string multiple times.

This change introduces a custom splitAndTrim function that
performs both operations in a single pass over the string,
reducing redundant work and improving performance, most notably
in the number of CPU cycles executed.

Metric Baseline Optimized Change
Time (ms) 35.57 35.36 -0.59%
Cycles 34.91M 34.26M -1.86%
Instructions 85.54M 85.24M -0.35%
Branch Misses 111.9K 112.2K +0.27%
Cache Misses 242.1K 239.9K -0.91%

Copy link
Contributor Author

ilovepi commented Sep 16, 2025

@llvmbot
Copy link
Member

llvmbot commented Sep 16, 2025

@llvm/pr-subscribers-llvm-support

Author: Paul Kirth (ilovepi)

Changes

The splitMustacheString function previously used a loop of
StringRef::split and StringRef::trim. This was inefficient as
it scanned each segment of the accessor string multiple times.

This change introduces a custom splitAndTrim function that
performs both operations in a single pass over the string,
reducing redundant work and improving performance, most notably
in the number of CPU cycles executed.

Metric Baseline Optimized Change
Time (ms) 35.57 35.36 -0.59%
Cycles 34.91M 34.26M -1.86%
Instructions 85.54M 85.24M -0.35%
Branch Misses 111.9K 112.2K +0.27%
Cache Misses 242.1K 239.9K -0.91%

Full diff: https://github.com/llvm/llvm-project/pull/159198.diff

1 Files Affected:

  • (modified) llvm/lib/Support/Mustache.cpp (+27-7)
diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp
index fcb55c4edd815..8139e9eb4717f 100644
--- a/llvm/lib/Support/Mustache.cpp
+++ b/llvm/lib/Support/Mustache.cpp
@@ -35,6 +35,32 @@ static bool isContextFalsey(const json::Value *V) {
   return isFalsey(*V);
 }
 
+static void splitAndTrim(StringRef Str, SmallVectorImpl<StringRef> &Tokens) {
+  size_t CurrentPos = 0;
+  while (CurrentPos < Str.size()) {
+    // Find the next delimiter.
+    size_t DelimiterPos = Str.find('.', CurrentPos);
+
+    // If no delimiter is found, process the rest of the string.
+    if (DelimiterPos == StringRef::npos) {
+      DelimiterPos = Str.size();
+    }
+
+    // Get the current part, which may have whitespace.
+    StringRef Part = Str.slice(CurrentPos, DelimiterPos);
+
+    // Manually trim the part without creating a new string object.
+    size_t Start = Part.find_first_not_of(" \t\r\n");
+    if (Start != StringRef::npos) {
+      size_t End = Part.find_last_not_of(" \t\r\n");
+      Tokens.push_back(Part.slice(Start, End + 1));
+    }
+
+    // Move past the delimiter for the next iteration.
+    CurrentPos = DelimiterPos + 1;
+  }
+}
+
 static Accessor splitMustacheString(StringRef Str, MustacheContext &Ctx) {
   // We split the mustache string into an accessor.
   // For example:
@@ -47,13 +73,7 @@ static Accessor splitMustacheString(StringRef Str, MustacheContext &Ctx) {
     // It's a literal, so it doesn't need to be saved.
     Tokens.push_back(".");
   } else {
-    while (!Str.empty()) {
-      StringRef Part;
-      std::tie(Part, Str) = Str.split('.');
-      // Each part of the accessor needs to be saved to the arena
-      // to ensure it has a stable address.
-      Tokens.push_back(Part.trim());
-    }
+    splitAndTrim(Str, Tokens);
   }
   // Now, allocate memory for the array of StringRefs in the arena.
   StringRef *ArenaTokens = Ctx.Allocator.Allocate<StringRef>(Tokens.size());

@ilovepi ilovepi force-pushed the users/ilovepi/mustache-single-pass branch from 410fdc8 to f5b3210 Compare September 22, 2025 17:07
@ilovepi ilovepi force-pushed the users/ilovepi/mustache-accessor-opt branch from b68de04 to 1acd655 Compare September 22, 2025 17:56
@ilovepi ilovepi force-pushed the users/ilovepi/mustache-single-pass branch 2 times, most recently from 2de3866 to c6e7926 Compare September 25, 2025 22:12
@ilovepi ilovepi force-pushed the users/ilovepi/mustache-accessor-opt branch 2 times, most recently from bd084ea to 735490e Compare September 26, 2025 01:55
@ilovepi ilovepi force-pushed the users/ilovepi/mustache-single-pass branch 2 times, most recently from 28deddc to ca0de5e Compare September 29, 2025 17:39
@ilovepi ilovepi force-pushed the users/ilovepi/mustache-accessor-opt branch 2 times, most recently from cb29414 to f36f86e Compare September 29, 2025 22:28
@ilovepi ilovepi force-pushed the users/ilovepi/mustache-single-pass branch 2 times, most recently from 919e389 to 943c634 Compare September 30, 2025 01:54
@ilovepi ilovepi force-pushed the users/ilovepi/mustache-accessor-opt branch 2 times, most recently from 8401695 to df30efc Compare September 30, 2025 03:47
@ilovepi ilovepi force-pushed the users/ilovepi/mustache-single-pass branch 2 times, most recently from 0cd6777 to d5cdf06 Compare September 30, 2025 03:48
@ilovepi ilovepi force-pushed the users/ilovepi/mustache-accessor-opt branch 2 times, most recently from 1e2990f to c6534b6 Compare October 1, 2025 00:04
@ilovepi ilovepi force-pushed the users/ilovepi/mustache-single-pass branch from d5cdf06 to 66ea253 Compare October 1, 2025 00:04
@ilovepi ilovepi force-pushed the users/ilovepi/mustache-accessor-opt branch from c6534b6 to bfe51da Compare October 1, 2025 00:15
@ilovepi ilovepi force-pushed the users/ilovepi/mustache-single-pass branch from 66ea253 to 520cdc1 Compare October 1, 2025 00:15
@ilovepi ilovepi force-pushed the users/ilovepi/mustache-accessor-opt branch from bfe51da to 0230277 Compare October 6, 2025 17:21
@ilovepi ilovepi force-pushed the users/ilovepi/mustache-single-pass branch from 520cdc1 to 37926e7 Compare October 6, 2025 17:21
@ilovepi ilovepi force-pushed the users/ilovepi/mustache-accessor-opt branch from 0230277 to d6ce86b Compare October 6, 2025 20:26
@ilovepi ilovepi force-pushed the users/ilovepi/mustache-single-pass branch 2 times, most recently from 7adaaa3 to 9afa122 Compare October 9, 2025 17:51
@ilovepi ilovepi force-pushed the users/ilovepi/mustache-accessor-opt branch from d6ce86b to ee52188 Compare October 9, 2025 17:51
The splitMustacheString function previously used a loop of
StringRef::split and StringRef::trim. This was inefficient as
it scanned each segment of the accessor string multiple times.

This change introduces a custom splitAndTrim function that
performs both operations in a single pass over the string,
reducing redundant work and improving performance, most notably
in the number of CPU cycles executed.

  Metric         | Baseline | Optimized | Change
  -------------- | -------- | --------- | -------
  Time (ms)      | 35.57    | 35.36     | -0.59%
  Cycles         | 34.91M   | 34.26M    | -1.86%
  Instructions   | 85.54M   | 85.24M    | -0.35%
  Branch Misses  | 111.9K   | 112.2K    | +0.27%
  Cache Misses   | 242.1K   | 239.9K    | -0.91%
@ilovepi ilovepi force-pushed the users/ilovepi/mustache-accessor-opt branch from ee52188 to 9808298 Compare October 10, 2025 16:21
@ilovepi ilovepi force-pushed the users/ilovepi/mustache-single-pass branch from 9afa122 to 7d30130 Compare October 10, 2025 16:21
Copy link
Member

@evelez7 evelez7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM w/ nit

size_t DelimiterPos = Str.find('.', CurrentPos);

// If no delimiter is found, process the rest of the string.
if (DelimiterPos == StringRef::npos) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can omit the curly braces here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants