Skip to content

Commit 29e37be

Browse files
committed
[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.
1 parent 55d0587 commit 29e37be

File tree

1 file changed

+22
-8
lines changed

1 file changed

+22
-8
lines changed

llvm/lib/Support/Mustache.cpp

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -428,26 +428,40 @@ class EscapeStringStream : public raw_ostream {
428428
public:
429429
explicit EscapeStringStream(llvm::raw_ostream &WrappedStream,
430430
EscapeMap &Escape)
431-
: Escape(Escape), WrappedStream(WrappedStream) {
431+
: Escape(Escape), EscapeChars(Escape.keys().begin(), Escape.keys().end()),
432+
WrappedStream(WrappedStream) {
432433
SetUnbuffered();
433434
}
434435

435436
protected:
436437
void write_impl(const char *Ptr, size_t Size) override {
437-
llvm::StringRef Data(Ptr, Size);
438-
for (char C : Data) {
439-
auto It = Escape.find(C);
440-
if (It != Escape.end())
441-
WrappedStream << It->getSecond();
442-
else
443-
WrappedStream << C;
438+
StringRef Data(Ptr, Size);
439+
size_t Start = 0;
440+
while (Start < Size) {
441+
// Find the next character that needs to be escaped.
442+
size_t Next = Data.find_first_of(EscapeChars.str(), Start);
443+
444+
// If no escapable characters are found, write the rest of the string.
445+
if (Next == StringRef::npos) {
446+
WrappedStream << Data.substr(Start);
447+
return;
448+
}
449+
450+
// Write the chunk of text before the escapable character.
451+
if (Next > Start)
452+
WrappedStream << Data.substr(Start, Next - Start);
453+
454+
// Look up and write the escaped version of the character.
455+
WrappedStream << Escape[Data[Next]];
456+
Start = Next + 1;
444457
}
445458
}
446459

447460
uint64_t current_pos() const override { return WrappedStream.tell(); }
448461

449462
private:
450463
EscapeMap &Escape;
464+
SmallString<8> EscapeChars;
451465
llvm::raw_ostream &WrappedStream;
452466
};
453467

0 commit comments

Comments
 (0)